async function validateReactionClip(file){ if(!file)return; if(!/\.(gif|mp4)$/i.test(file.name))throw Error('Reaction usa GIF animado o MP4.'); if(/\.mp4$/i.test(file.name)){const url=URL.createObjectURL(file);try{await new Promise((resolve,reject)=>{const video=document.createElement('video');video.preload='metadata';video.onloadedmetadata=()=>resolve();video.onerror=()=>reject(Error('No se pudo leer el MP4.'));video.src=url;});}finally{URL.revokeObjectURL(url);}} } function AuthoringMediaGuide({activity,layout}){ const recommendations={reaction:layout==='embedded'?'Tarjeta completa: PNG o JPG, vertical 9:16; recomendado 900 x 1600 px. Incluye pregunta y opciones.':'Material central: GIF animado (preferido) o MP4 de cualquier duracion. Se reproduce en loop. Recomendamos clips de unos 3 segundos. Recomendado 1600 x 900 px (16:9); la tarjeta es vertical 900 x 1600 px (9:16). Exporta el GIF con repeticion infinita.',zero:'Imagen opcional: PNG, JPG, WebP o GIF; recomendado 1600 x 900 px (16:9). Video: MP4 o WebM, 1280 x 720 px (16:9). Escribe la pregunta y opciones en el formulario.',vocabulary:'Sube solamente el GIF central, sin título, palabra ni botones. Recomendado 640 x 640 px (1:1). Declara palabra, categoría y, para verbos, las formas 1, 2 y 3.',alien:'Escena de Alien Contact (ELT): GIF, PNG, JPG o WebP; recomendado 1600 x 900 px (16:9). Video: MP4 o WebM, 1280 x 720 px (16:9).',mirror:'Imagen: PNG, JPG, WebP o GIF; recomendado 1600 x 900 px (16:9). Video: MP4 o WebM, 1280 x 720 px (16:9).'}; return ; } function TopicAdministration({catalog,request,onChanged}){ const [id,setId]=React.useState(''),[name,setName]=React.useState(''),[version,setVersion]=React.useState(0),[category,setCategory]=React.useState('completeIdea'),[error,setError]=React.useState(''); return

Topics y subtopics

t7 agrupa t7.1, t7.2 y t7.3. Puedes cambiar el topic principal de cada ejercicio desde Ver / editar. Cambiar su clasificación no borra el ejercicio.

{e.preventDefault();try{await request('/authoring/topics',{method:'POST',body:JSON.stringify({id,name,version,category,parentId:id.includes('.')?id.slice(0,id.lastIndexOf('.')):null})});onChanged(await request('/authoring/catalog'));setId('');setName('');setVersion(0);setError('');}catch(err){setError(err.message);}}}>
{error&&

{error}

}{catalog.topics.map(t=>
{t.id} · {t.name}
)}
; } function MakerCardPreview({exercise}){ const [src,setSrc]=React.useState(''),[error,setError]=React.useState(''); React.useEffect(()=>{let live=true,url='';setError('');if(exercise.layout==='embedded'){setSrc(exercise.media);return;}window.SpeedCard.generate(exercise,s=>s).then(card=>{url=URL.createObjectURL(card.blob);if(live)setSrc(url);}).catch(e=>{if(live)setError(e.message);});return()=>{live=false;if(url)URL.revokeObjectURL(url);};},[exercise.layout,exercise.media,exercise.question,JSON.stringify(exercise.options)]); const zones=exercise.layout==='embedded'?exercise.hotspots:window.SpeedCard.defaults(exercise.options.length); return
{error&&

{error}

}
{src&&Tarjeta Reaction}{src&&exercise.layout==='generated'&&window.SpeedCard.animated(exercise.media)&&(/\.mp4(?:[?#]|$)/i.test(exercise.media)?
; } function ExerciseContentPreview({exercise,compact=false}){ const options=exercise.options||[],valid=(exercise.validAnswers||[]).filter(a=>a.trim()),hasOptions=['reaction','zero'].includes(exercise.type)&&options.length>0; const media=(typeof exercise.media==='string'?exercise.media:exercise.media?.src||'').replace('DINAMICS/JUEGOS/MATE/','DINAMICS/JUEGOS/WEB/MATE/'); const video=/\.(mp4|webm|mov)(?:[?#]|$)/i.test(media); return
{compact?'Contenido del ejercicio':'Vista previa en vivo'}{exercise.type==='vocabulary'?'Vocabulario':media?'Con recurso visual':'Ejercicio de texto'}
{!compact&&

{exercise.title||'Tu nuevo ejercicio'}

} {exercise.type==='reaction'&&exercise.layout?:media&&
{video?
}

{exercise.question||exercise.word||(video?'Reproduce el video para revisar la pregunta y las respuestas incluidas.':compact?'Este registro no incluye una pregunta de texto.':'Escribe la pregunta o consigna: aparecerá aquí tal cual.')}

{hasOptions&&
    {options.map((option,i)=>{const label=typeof option==='string'?option:option.label;const correct=typeof option==='string'?exercise.correct===i:exercise.correct===option.value;return
  1. {i+1}{label||'Respuesta '+(i+1)+' por escribir'}{correct&&✓ Correcta}
  2. ;})}
} {['alien','mirror'].includes(exercise.type)&&
Respuestas completas válidas{valid.length?
    {valid.map((answer,i)=>
  • {answer}✓ Válida
  • )}
:!compact&&

Agrega las respuestas válidas para revisarlas aquí.

}
} {exercise.type==='alien'&&exercise.finalWords?.some(Boolean)&&

Palabras finales: {exercise.finalWords.filter(Boolean).join(' · ')}

} {exercise.type==='vocabulary'&&exercise.variants?.some(Boolean)&&

Variantes: {exercise.variants.filter(Boolean).join(' · ')}

} {!compact&&Se actualiza mientras escribes. El verde identifica la respuesta correcta o válida.}
; } function EducationalAuthoring({user,baseUrl}){ const [catalog,setCatalog]=React.useState(null),[items,setItems]=React.useState([]),[tab,setTab]=React.useState('create'),[error,setError]=React.useState(''),[notice,setNotice]=React.useState(''),[busy,setBusy]=React.useState(false),[selected,setSelected]=React.useState(null),[search,setSearch]=React.useState(''),[libraryType,setLibraryType]=React.useState(''),[mediaType,setMediaType]=React.useState('reaction'); const empty=type=>({type,title:'',primaryTopic:'',connectedTopics:[],difficulty:'f',contexts:type==='alien'?['elt']:['pap','elt'],layout:'generated',hotspots:[],cardMedia:'',distribution:'GENERAL',masterIds:[],question:'',options:['',''],correct:0,media:'',validAnswers:[],ideaKind:'affirmative',contactMode:'all',contactGroups:[],contactCategoryIds:[],contactRoleCodes:[],learning:{structure:.35,logic:.05},zeroMode:'normal',category:'',word:'',variants:[],unlock:'',finalWords:[]}); const [form,setForm]=React.useState(()=>empty('reaction')); const request=(route,options)=>window.TeacherRoomClient.request(baseUrl,user,route,options); const resolveMedia=src=>String(src||'').startsWith('/authoring/')?baseUrl.replace(/\/$/,'')+src:src; async function upload(file,activity=form.type,purpose="materiales"){if(!file)return;if(file.size>20*1024*1024)throw Error('El archivo supera 20 MB. Reduce su tamaño.');const result=await request('/authoring/media?activity='+activity+'&purpose='+purpose,{method:'POST',headers:{'Content-Type':'application/octet-stream','X-File-Name':encodeURIComponent(file.name||'reaction-tarjeta.png')},body:file});const c=await request('/authoring/catalog');setCatalog(c);return result.src;} const update=(key,value)=>{setForm(prev=>({...prev,[key]:value}));setNotice('');}; async function reload(){const data=await request('/authoring/content');setItems(data.items);} React.useEffect(()=>{let live=true;Promise.all([request('/authoring/catalog'),request('/authoring/content')]).then(([c,data])=>{if(live){setCatalog(c);setItems(data.items);}}).catch(e=>{if(live)setError(e.message);});return()=>{live=false;};},[user.id,baseUrl]); async function save(event){event.preventDefault();if(busy)return;setBusy(true);setError('');setNotice('');try{const payload={...form};if(payload.type==='vocabulary'){payload.title=payload.word;if(payload.category==='activity')payload.forms={a:{word:payload.forms?.a?.word||payload.word},b:{word:payload.forms?.b?.word||''},c:{word:payload.forms?.c?.word||''}};}if(payload.type==='zero'&&payload.zeroMode==='contact'){payload.contacts={person:{categoryIds:payload.contactCategoryIds.length?payload.contactCategoryIds:['c1'],roleCodes:payload.contactRoleCodes||[],mode:payload.contactMode==='all'?'only':payload.contactMode}};payload.question=payload.question.replaceAll('____','{{contact:person}}');payload.options=payload.options.map(s=>s.replaceAll('____','{{contact:person}}'));}for(const key of ['validAnswers','variants','finalWords'])payload[key]=payload[key].map(s=>s.trim()).filter(Boolean);if(payload.type==='reaction'){if(payload.layout==='generated'){const card=await window.SpeedCard.generate(payload,resolveMedia);payload.cardMedia=await upload(card.blob,'reaction','terminados');payload.hotspots=card.hotspots;}else{const response=await fetch(resolveMedia(payload.media));if(!response.ok)throw Error('No se pudo copiar la tarjeta terminada.');payload.cardMedia=await upload(await response.blob(),'reaction','terminados');}}const result=await request('/authoring/content',{method:'POST',body:JSON.stringify({id:selected?.id,version:selected?.version,payload})});setSelected({...result,payload});setForm(payload);setNotice('Borrador guardado en el servidor.');await reload();}catch(e){setError(e.message);}finally{setBusy(false);}} async function state(item,next){if(busy)return;let note='';if(['corrections','rejected'].includes(next)){note=window.prompt('Explicación para el autor:');if(note===null)return;}if(next==='archived'&&!window.confirm('¿Archivar este contenido conservando sus versiones?'))return;setBusy(true);setError('');try{await request('/authoring/content/'+encodeURIComponent(item.id)+'/state',{method:'POST',body:JSON.stringify({version:item.version,state:next,note})});await reload();setNotice(next==='pending'?'Propuesta enviada a p para supervisión.':'Estado actualizado.');}catch(e){setError(e.message);}finally{setBusy(false);}} function open(item){setSelected(item);setForm({...empty(item.payload.type),...item.payload});setTab(item.payload.type==='vocabulary'?'vocabulary':'create');setNotice('');} const lines=value=>value.split('\n'); const multi=(name,values,options)=>
{name}{options.map(({id,label})=>)}
; if(!catalog)return
{error?

{error}

:

Cargando catálogos oficiales…

}
; const mediaLabel=src=>catalog.mediaEntries?.find(m=>m.src===src)?.name||src; const typeNames={reaction:'Reaction',alien:'Alien Contact',mirror:'Mirror Reflection',zero:'Zero Error',vocabulary:'VOC Maker — Agregar vocabulario'}; const filtered=items.filter(item=>(!libraryType||item.payload.type===libraryType)&&(item.payload.title+' '+item.payload.type+' '+item.state+' '+item.payload.primaryTopic).toLowerCase().includes(search.toLowerCase())); return

Autoría educativa

{catalog.admin?'Supervisa las propuestas de tus colaboradores en Aprobaciones.':'Crea un borrador y envíalo a p desde Mis propuestas.'}

Los ejercicios aprobados se incorporan al recargar las actividades. Reaction y Zero Error comparten el banco PAP / ELT por topic y dificultad.

{error&&

{error}

}{notice&&

{notice}

} {['create','vocabulary'].includes(tab)&&
{tab==='create'&&}

{typeNames[form.type]}

{form.type==='reaction'&&}{form.type==='reaction'&&}{form.type!=='vocabulary'&&} {form.type==='vocabulary'?<>{form.category==='activity'&&
Las tres formas del verbo{[['a','Forma 1 - Description','#006b77'],['b','Forma 2 - Activity','#b23a48'],['c','Forma 3 - Activity','#8b3fb0']].map(([key,label,color])=>)}El GIF central se comparte. Cada forma conserva su palabra y su color.
}:<>{form.type==='alien'?multi('Contextos',form.contexts,[{id:'pap',label:'PAP - revisión personal'},{id:'elt',label:'ELT - entrenamiento'}]):

Banco compartido PAP / ELT. La casilla y el alumno seleccionan topic y dificultad.

}}
Topics conectados{form.connectedTopics.map(id=>

{id} · {catalog.topics.find(t=>t.id===id)?.name}

)}
{multi('Especialidades m',form.masterIds,catalog.masters.map(m=>({id:m.id,label:m.label})))} {['reaction','alien','zero','vocabulary'].includes(form.type)&&} {form.type==='reaction'&&
Botones dentro de la imagen{form.layout==='embedded'&&<>

Posiciones en porcentajes de la imagen completa. Ajusta cada zona viendo la vista previa.

{form.options.map((o,i)=>{const r=form.hotspots[i]||window.SpeedCard.defaults(form.options.length)[i];return
Zona {i+1}: {o}{['x','y','w','h'].map(k=>)}
;})}}
} {form.type!=='vocabulary'&&