/* global React */
/* eslint-disable */
const { useState: useStateEXT, useEffect: useEffectEXT, useRef: useRefEXT } = React;
const gm = window.getMsg;
/* ---------- Features to explore ---------- */
const EX_FEATURES = [
{ id:'add', emoji:'➕', name: gm('Agregar enlace','Ajouter un lien','Add a link'), sub: gm('Crear recurso externo','Créer une ressource externe','Create external resource'),
desc: gm('Al hacer clic en "+ Créer" se abre un formulario: nombre, URL, descripción, idioma y visibilidad. Como crear una entrada en tu biblioteca personal de herramientas.', 'En cliquant sur "+ Créer" un formulaire s\'ouvre : nom, URL, description, langue et visibilité. Comme créer une entrée dans ta bibliothèque d\'outils.', 'Clicking "+ Créer" opens a form: name, URL, description, language and visibility. Like creating an entry in your personal tools library.') },
{ id:'search', emoji:'🔍', name: gm('Buscar y filtrar','Rechercher et filtrer','Search and filter'), sub: gm('Búsqueda en tiempo real','Recherche en temps réel','Real-time search'),
desc: gm('La barra de búsqueda filtra en tiempo real. Los filtros de pestaña te permiten ver: Todos, Míos, Compartidos, Públicos o solo los Privados.', 'La barre de recherche filtre en temps réel. Les filtres d\'onglet permettent de voir : Tous, Miens, Partagés, Publics ou seulement Privés.', 'The search bar filters in real time. Tab filters let you view: All, Mine, Shared, Public or just Private.') },
{ id:'vis', emoji:'🔒', name: gm('Público o Privado','Public ou Privé','Public or Private'), sub: gm('Control de visibilidad','Contrôle de visibilité','Visibility control'),
desc: gm('Cuando creas un enlace como Público, aparece en el directorio comunitario. Si es Privado, solo tú lo ves. Puedes cambiar la visibilidad en cualquier momento haciendo clic en el estado.', 'Quand tu crées un lien en Public, il apparaît dans le répertoire communautaire. S\'il est Privé, seul toi le vois. Tu peux changer la visibilité à tout moment en cliquant sur l\'état.', 'When you create a link as Public, it appears in the community directory. If Private, only you see it. You can change visibility anytime by clicking the status.') },
{ id:'collab', emoji:'🌐', name: gm('Base colectiva','Base collective','Collective base'), sub: gm('Directorio comunitario','Répertoire communautaire','Community directory'),
desc: gm('Cuando alguien marca un recurso como Público, aparece en la pestaña "Públicos" para todos. La plataforma construye colectivamente un directorio curado de herramientas para crear con IA.', 'Quand quelqu\'un marque une ressource comme Publique, elle apparaît dans l\'onglet "Publics" pour tous. La plateforme construit collectivement un répertoire d\'outils pour créer avec l\'IA.', 'When someone marks a resource as Public, it appears in the "Public" tab for everyone. The platform collectively builds a curated directory of AI creation tools.') },
];
/* ---------- Confetti (deterministic positions) ---------- */
const CF_DATA = Array.from({ length: 24 }, (_, i) => ({
id: i,
left: ((i * 4.37) % 96).toFixed(1),
delay: ((i * 0.15) % 2.8).toFixed(2),
color: ['#6366f1','#ec4899','#f59e0b','#22c55e','#06b6d4','#a855f7','#ef4444'][i % 7],
w: 8 + (i % 5) * 1.5,
h: 12 + (i % 4) * 2,
}));
/* ---------- Tour steps ---------- */
const EX_STEPS = [
{ id:'welcome', progress:5,
msgs:[
{ from:'aimy', kind:'recall' },
{ from:'aimy', text: gm('¡Bienvenido al Módulo Final! 🎉 Estás a un paso de convertirte en Creador AIMaker Certificado.', 'Bienvenue dans le Module Final ! 🎉 Tu es à un pas de devenir Créateur AIMaker Certifié.', 'Welcome to the Final Module! 🎉 You\'re one step away from becoming a Certified AIMaker Creator.') },
{ from:'aimy', text: gm('Módulo 6: Acceso Externo — tu directorio personal de herramientas y recursos de internet.', 'Module 6 : Accès Externe — ton répertoire personnel d\'outils et de ressources internet.', 'Module 6: External Access — your personal directory of tools and internet resources.') },
],
quickReplies:[gm('¡Al último módulo!','Au dernier module !','To the last module!'), gm('¿Qué es Acceso Externo?','C\'est quoi Accès Externe ?','What is External Access?')],
},
{ id:'value', progress:15,
msgs:[
{ from:'aimy', text: gm('"Una base de conocimiento colectiva de herramientas útiles para crear con IA."', '"Une base de connaissance collective d\'outils utiles pour créer avec l\'IA."', '"A collective knowledge base of useful tools for creating with AI."'), kind:'quote' },
{ from:'aimy', text: gm('Guarda, organiza y comparte los links más valiosos de tu kit de creación — desde plataformas de IA hasta APIs, generadores y referencias.', 'Sauvegarde, organise et partage les liens les plus précieux de ton kit — des plateformes IA aux APIs, générateurs et références.', 'Save, organize and share the most valuable links in your creation kit — from AI platforms to APIs, generators and references.') },
{ from:'aimy', kind:'exstats' },
],
},
{ id:'features', progress:35,
spotlight:'[data-tour="ext-create"]',
spotlightLabel: gm('4 funcionalidades clave','4 fonctionnalités clés','4 key features'),
msgs:[
{ from:'aimy', text: gm('Acceso Externo tiene 4 funcionalidades clave. Haz clic en cada una para conocerla. Explóralas todas para continuar.', 'Accès Externe a 4 fonctionnalités clés. Clique sur chacune pour la connaître. Explore-les toutes pour continuer.', 'External Access has 4 key features. Click on each to learn about it. Explore them all to continue.') },
{ from:'aimy', kind:'features' },
],
gate: ctx => ctx.exploredFeatures.size >= 4,
gateHint: gm('Explora las 4 funcionalidades para continuar','Explore les 4 fonctionnalités pour continuer','Explore all 4 features to continue'),
},
{ id:'sim-1', progress:52,
spotlight:'[data-tour="ext-create"]',
spotlightLabel: gm('Haz clic en + Créer','Clique sur + Créer','Click + Créer'),
msgs:[
{ from:'aimy', text: gm('¡Perfecto! Ahora practícalo tú mismo.', 'Parfait ! Maintenant pratique-le toi-même.', 'Perfect! Now practice it yourself.') },
{ from:'aimy', text: gm('Haz clic en + Créer arriba a la derecha y agrega tu primera herramienta externa.', 'Clique sur + Créer en haut à droite et ajoute ton premier outil externe.', 'Click + Créer at the top right and add your first external tool.') },
{ from:'aimy', kind:'simprog' },
],
gate: ctx => ctx.created >= 1,
gateHint: gm('Agrega tu primer enlace externo','Ajoute ton premier lien externe','Add your first external link'),
},
{ id:'sim-2', progress:66,
spotlight:'[data-tour="ext-create"]',
spotlightLabel: gm('Un enlace más','Un lien de plus','One more link'),
msgs:[
{ from:'aimy', text: gm('¡Genial! 🎉 Ya tienes el primero. Agrega un enlace más para completar el reto.', 'Super ! 🎉 Tu as le premier. Ajoute un lien de plus pour compléter le défi.', 'Great! 🎉 You have the first one. Add one more link to complete the challenge.') },
{ from:'aimy', kind:'simprog' },
],
gate: ctx => ctx.created >= 2,
gateHint: gm('Agrega un segundo enlace externo','Ajoute un second lien externe','Add a second external link'),
},
{ id:'sim-3', progress:78,
spotlight:'[data-tour="ext-visibility"]',
spotlightLabel: gm('Cambia a Público','Passe en Public','Switch to Public'),
msgs:[
{ from:'aimy', text: gm('¡Dos enlaces creados! Ahora contribuye a la comunidad.', 'Deux liens créés ! Maintenant contribue à la communauté.', 'Two links created! Now contribute to the community.') },
{ from:'aimy', text: gm('Haz clic en el estado de visibilidad de uno de tus recursos y cámbialo a Público.', 'Clique sur l\'état de visibilité d\'une de tes ressources et change-le en Public.', 'Click on the visibility status of one of your resources and change it to Public.') },
{ from:'aimy', kind:'simprog' },
],
gate: ctx => ctx.madePublic,
gateHint: gm('Cambia un enlace a Público para compartirlo con la comunidad','Change un lien en Public pour le partager avec la communauté','Change a link to Public to share it with the community'),
},
{ id:'quiz', progress:92,
msgs:[
{ from:'aimy', text: gm('¡Reto completado! 🎉 Tu recurso ya está en el directorio de la comunidad AIMaker.', 'Défi accompli ! 🎉 Ta ressource est maintenant dans le répertoire de la communauté AIMaker.', 'Challenge complete! 🎉 Your resource is now in the AIMaker community directory.') },
{ from:'aimy', text: gm('Último quiz de toda la ruta. 3 preguntas · +25 pts por respuesta correcta:', 'Dernier quiz de tout le parcours. 3 questions · +25 pts par bonne réponse :', 'Last quiz of the entire path. 3 questions · +25 pts per correct answer:') },
{ from:'aimy', kind:'quiz' },
],
gate: ctx => ctx.quizDone,
gateHint: gm('Responde las 3 preguntas para finalizar','Réponds aux 3 questions pour terminer','Answer all 3 questions to finish'),
},
{ id:'done', progress:100,
msgs:[
{ from:'aimy', kind:'badge' },
{ from:'aimy', text: gm('Has completado la Ruta de Capacitación completa de AIMaker. ¡Eres un experto certificado en todo lo que la plataforma tiene para ofrecer! 🏆', 'Tu as complété la Ruta de Capacitación completa de AIMaker. Tu es un expert certifié dans tout ce que la plateforme a à offrir ! 🏆', 'You\'ve completed the complete AIMaker Training Path. You\'re a certified expert in everything the platform has to offer! 🏆') },
{ from:'aimy', kind:'nextsection' },
],
},
];
/* ---------- Stats card ---------- */
function ExStats() {
return (
∞
{gm('Links que puedes guardar','Liens que tu peux sauvegarder','Links you can save')}
{gm('¡Hola! 👋 Soy DIAS. Te guío por el módulo final de AIMaker.', '👋 Bonjour ! Je suis DIAS. Je te guide dans le module final d\'AIMaker.', '👋 Hi! I\'m DIAS. I\'ll guide you through AIMaker\'s final module.')}
{gm('¡Hola de nuevo! 🎯 Llevas','Rebonjour ! 🎯 Tu as','Welcome back! 🎯 You have')} {completed.length} {modLabel} {gm('y','et','and')} {p.points} {window.t('quiz.pts')}. {gm('¡Ya casi terminas la ruta!','Tu es presque au bout du parcours !','You\'re almost done with the path!')}
{p.badges.length > 0 && (
{p.badges.map(b => 🛡️ {b})}
)}
);
}
/* ---------- Quiz ---------- */
const EX_QUIZ = [
{ q: gm('¿Qué puedes guardar en Acceso Externo?','Que peux-tu sauvegarder dans Accès Externe ?','What can you save in External Access?'),
opts:[
gm('Solo las herramientas que ya vienen en AIMaker','Seulement les outils déjà inclus dans AIMaker','Only tools already included in AIMaker'),
gm('Links de cualquier herramienta externa: plataformas de IA, APIs, referencias...','Liens de n\'importe quel outil externe : plateformes IA, APIs, références...','Links to any external tool: AI platforms, APIs, references...'),
gm('Únicamente código fuente y carpetas de proyecto','Uniquement le code source et des dossiers de projet','Only source code and project folders'),
],
correct: 1,
},
{ q: gm('¿Qué ocurre cuando marcas un recurso como Público?','Que se passe-t-il quand tu marques une ressource comme Publique ?','What happens when you mark a resource as Public?'),
opts:[
gm('Se borra de tu lista privada','Il est supprimé de ta liste privée','It gets deleted from your private list'),
gm('Aparece en el directorio comunitario y cualquier usuario puede acceder a él','Il apparaît dans le répertoire communautaire et tout utilisateur peut y accéder','It appears in the community directory and any user can access it'),
gm('Solo lo ven tus contactos directos','Seuls tes contacts directs le voient','Only your direct contacts see it'),
],
correct: 1,
},
{ q: gm('¿Puedes cambiar la visibilidad de un recurso después de crearlo?','Peux-tu changer la visibilité d\'une ressource après l\'avoir créée ?','Can you change the visibility of a resource after creating it?'),
opts:[
gm('No, la visibilidad es definitiva al crearlo','Non, la visibilité est définitive à la création','No, visibility is final when created'),
gm('Sí, haciendo clic en el estado del recurso en cualquier momento','Oui, en cliquant sur l\'état de la ressource à tout moment','Yes, by clicking on the resource\'s status at any time'),
gm('Solo durante las primeras 24 horas','Seulement pendant les premières 24 heures','Only during the first 24 hours'),
],
correct: 1,
},
];
function ExQuiz({ onDone }) {
const [i, setI] = useStateEXT(0);
const [picked, setPicked] = useStateEXT(null);
const [points, setPoints] = useStateEXT(0);
const [failed, setFailed] = useStateEXT(false);
function pick(idx) {
if (picked !== null) return;
setPicked(idx);
const np = points + (idx === EX_QUIZ[i].correct ? 25 : 0);
setPoints(np);
setTimeout(() => {
if (i + 1 >= EX_QUIZ.length) {
if (np === 75) { onDone(np); }
else { setFailed(true); }
} else { setI(i + 1); setPicked(null); }
}, 950);
}
if (failed) {
const correct = Math.round(points / 25);
return (
😅
{correct}/3 {gm('correctas. Necesitas las 3 para continuar.','correctes. Il te faut les 3 pour continuer.','correct. You need all 3 to continue.')}