1. Propósito de este manual

Este manual está dirigido a desarrolladores que quieran reimplementar el intérprete de QBJr. en otro lenguaje, target o plataforma. Documenta el contrato de cada pieza del sistema para mantener compatibilidad con programas .qbjr existentes.

Para uso del Player como usuario final → Manual del Player. Para la especificación completa del lenguaje → Especificación. Para la historia de las decisiones y bugs → Evolución del Proyecto.

📌 REFERENCIALa implementación de referencia está en JavaScript vanilla (browser). Los contratos documentados son independientes de la plataforma. El §Portabilidad cubre consideraciones específicas para C++/SDL2 y C#/Godot.

2. Pipeline de ejecución

El pipeline es completamente lineal y síncrono, con un único punto de asincronía controlada (yield en el Generator del intérprete):

Fuente .qbjrTexto plano UTF-8. Soporte de acentos y ñ.
qbjr-lexer.jsTokenizador. Produce: [{type, value, line, col}]
qbjr-parser.jsParser recursive descent. Produce: { body:[], subs:{}, errors:[] }
qbjr-interpreter.jsGenerator function*. Recorre el AST nodo a nodo. yield "frame" → requestAnimationFrame
Módulos de runtimecanvas · input · sprites · audio · layers · tiles · physics · files · text
qbjr-engine.jsCoordinador. Ordena el tick por frame y copia virtual→display (CSS pixel-perfect).
📌 DECISIÓN CENTRALEl intérprete usa JavaScript Generators (function*) para ejecución cooperativa. Cada ciclo del game loop, el engine llama gen.next() hasta encontrar yield "frame" o result.done. Esto elimina timers, callbacks y async/await en el código del intérprete. En otras plataformas: C++20 coroutines, C# async/await, Python generators.

3. Orden de tick por frame

⚠ ORDEN CRÍTICOphysicsRT.tick() debe ir después de tilesRT.tick(). Si se invierte, la resolución de colisiones usa el estado de tiles del frame anterior — los sprites atraviesan superficies en un frame de retraso visible.
// Cada frame en qbjr-engine.js: 1. inputRT.frameReset() // limpiar estados click/suelta del frame anterior 2. gen.next() × N (límite: 5000) // ejecutar QBJr. hasta yield "frame" 3. layersRT.tick(dt) // avanzar scroll y parallax 4. tilesRT.tick(layersRT) // dibujar tiles en sus capas 5. physicsRT.tick(dt, sprites, tiles) // física — DESPUÉS de tiles 6. spritesRT.tick(ctx, dt) // animar y dibujar sprites 7. layersRT.composite(ctx, vw, vh) // componer capas sobre virtual canvas 8. copiarADisplay() // escalar al display canvas
⚠ BUG HISTÓRICO — dt = 0Calcular const _dt = now - lastFrame antes de lastFrame = now. El orden inverso produce _dt = 0 en todos los frames. La física queda estática y las animaciones no avanzan sin error visible.

4. Lexer

TIPODESCRIPCIÓN
KEYWORDPalabras clave normalizadas a UPPERCASE internamente.
IDENTIFIERVariables y subprogramas. Preserva el case original del fuente.
NUMBERLiterales numéricos: enteros y flotantes.
STRINGLiterales de texto entre comillas dobles. Soporte acentos y ñ.
OPERATOR= + - * / ^ \ < > <= >= <>
LPAREN/RPAREN( )
LBRACKET/RBRACKET[ ] — índices de arreglos.
NEWLINEFin de instrucción. Significativo: no se pueden poner dos comandos en una línea.
EOFFin del stream.
⚠ REGLA #1 — OPERADORES LÓGICOSAND OR XOR NOT MOD DIV son KEYWORD solo en MAYÚSCULAS. En minúsculas son IDENTIFIER válidos. and, or, not pueden ser nombres de variable. Esta es la regla más importante del lexer.
⚠ REGLA #2 — LA LETRA AA es IDENTIFIER en el lexer, nunca KEYWORD. El parser la reconoce contextualmente como separador de rango en PARA i DE 1 A 10. Si fuera KEYWORD, sería imposible usarla como nombre de variable.

5. Parser

Recursive descent — 8 niveles de precedencia

parseExpr() → OR, XOR (menor precedencia) parseAnd() → AND parseNot() → NOT (unario) parseComparison() → = <> < > <= >= COLISIONA (infijo) parseAddSub() → + - parseMulDiv() → * / MOD DIV parsePower() → ^ \ (asocia a la derecha) parseUnary() → - (negación unaria) parsePrimary() → literales, identifiers, llamadas, paréntesis

Comandos de dos palabras

El parser detecta pares de keywords adyacentes y los emite como un único nodo Command con guión bajo. El mapa twoWordCmds tiene 30+ pares:

CREAR + SPRITE → CREAR_SPRITE CREAR + TILESET → CREAR_TILESET FISICA + GRAVEDAD → FISICA_GRAVEDAD PONER + TILE → PONER_TILE CARGAR + DATOS → CARGAR_DATOS FIN + SI → FIN_SI FIN + MIENTRAS → FIN_MIENTRAS FIN + PARA → FIN_PARA ... (30+)

Pre-pasada de subprogramas

📌Los bloques DEFINIR…FIN_DEFINIR se extraen en una primera pasada antes del parse principal. Esto permite invocarlos con HACER en cualquier punto del programa, incluyendo antes de su definición textual. El AST resultante: { body:[], subs:{ NOMBRE:[nodos] } }.

Estructura del AST

// Raíz: { body:[nodo,...], subs:{ NOMBRE:[nodo,...] }, errors:[...] } // Nodos más comunes: {type:"Command", cmd:"CREAR_SPRITE", args:[...]} {type:"Assign", target:"x", index:null, value:expr} {type:"If", cond:expr, thenBlock:[...], elseIfs:[...], elseBlock:[...]} {type:"While", cond:expr, body:[...]} {type:"For", var:"i", from:expr, to:expr, step:expr, body:[...]} {type:"Select", expr:expr, cases:[{val,body}], defaultCase:[...]} {type:"Call", name:"NOMBRE"} // HACER subprograma {type:"FuncCall", name:"SEN", args:[...]} {type:"BinaryExpr",op:"+", left:expr, right:expr} {type:"UnaryExpr", op:"NOT", operand:expr} {type:"Identifier",name:"x"} {type:"IndexedIdent",name:"arr", index:expr} {type:"Literal", value:42} {type:"KeyCombo", keys:["FLECHA_ARRIBA","Z"]}
⚠ TRAMPA — SI NO (else)"SI NO" en el stream de tokens parece el inicio de "SI NOT condición". En parseBlock(): si el token actual es SI y el siguiente es NO, detener el bloque sin consumir esos tokens. Luego parsear SI+NO como bloque else. Sin este lookahead, el else nunca ejecuta y no hay error de parseo — el bug es silencioso.

6. Intérprete

Arquitectura Generator

// El intérprete cede el control al game loop: function* ejecutarNodo(nodo, estado) { // ... lógica del nodo ... yield SIG_FRAME; // "esperar al próximo frame" } // El engine avanza el generator cada frame: let steps = 0; do { result = gen.next(); steps++; } while (!result.done && result.value !== SIG_FRAME && steps < 5000); // Si steps >= 5000: advertencia, no bloqueo. El programa sigue.

Señales del generator

SEÑALSIGNIFICADO
yield "frame" (SIG_FRAME)Cede control al game loop. El engine retoma en el próximo requestAnimationFrame.
{__signal__:"break"} (SIG_BREAK)Propaga ESCAPAR hacia arriba en la cadena de generators anidados.

Estado del intérprete

const estado = { vars: {}, // variables globales { clave: valor } subs: {}, // subprogramas { NOMBRE: [nodos] } paleta: {}, // paleta de colores activa colorTrazo: "#FFF", colorRelleno: "#FFF", colorFondo: "#000", grosor: 1, errores: [], finalizar: false, pantalla: { w:256, h:240 }, runtime: { canvas, input, sprites, audio, layers, tiles, physics, files, text }, };

7. Helpers críticos

Estos helpers son el resultado de bugs reales. Cada uno resuelve una ambigüedad específica del lenguaje que no puede resolverse con un evaluador naïve.

varKey(name, estado)
Lookup case-insensitive de variables. Busca: exacto → lowercase → uppercase → case-insensitive completo. Retorna la clave canónica existente o name si es nueva.
→ Usar en toda asignación y lectura de variable.
rawS(i)
En Command: retorna el nombre literal del Identifier en args[i] SIN evaluar como variable. PALETA retro → rawS devuelve "retro", no vars["retro"] que sería 0.
→ Usar en: PALETA, COLOR (nombre), VOLTEAR, FONDO, SCROLL y todo cmd con constantes nombradas.
argNom(i)
En FuncCall: retorna el nombre raw del rawArg[i] SIEMPRE como literal, nunca evalúa. Los args de FuncCall se evalúan antes del switch — argNom accede al rawArg antes de esa evaluación.
→ Usar en: COLISIONA(), EN_SUELO(), FIN_ARCHIVO(), MOUSE_ENCIMA(), VEL_X(), VEL_Y(), COLISIONA_TILE().
argStr(i) v0.9.3+
Semántica híbrida para LEER_DATO: si el Identifier existe como variable string no vacía → usa su valor (uso dinámico: salaKey="sala_3"). Si es TODO_MAYÚSCULAS → lowercase como clave literal. Si es mixto no-variable → nombre literal como clave. Nunca usar argNom() para LEER_DATO.
→ Usar SOLO en: LEER_DATO. La distinción argStr/argNom es la fuente del bug T-04.
yieldPromise(promise)
Suspende el generator hasta que la Promise se resuelva (máximo 5 segundos). El game loop sigue corriendo mientras espera. Hace la asincronía completamente transparente para el programador QBJr.
→ Usar en: ABRIR (fetch/localStorage) y CARGAR_DATOS.
hasFlag(name) / N(i) / S(i)
hasFlag: VERDADERO si el nombre literal aparece en args del Command actual. Para flags opcionales sin valor. N(i): alias de evalExpr(args[i]) para números. S(i): alias String(evalExpr(args[i])) para strings evaluados.
→ hasFlag en: RIGIDO, RELLENO, BUCLE, CERRAR, HITBOX. N(i) y S(i) para coordenadas y nombres evaluados.

8. Runtime Canvas

⚠ FIRMA CRÍTICA — rectangulo()La firma correcta es rectangulo(x, y, ancho, alto). Implementarla como (x, y, alto, ancho) hace que todas las barras horizontales aparezcan verticales. Test inmediato: RECTANGULO 10 10 100 20 RELLENO debe dibujar una barra horizontal de 100×20.
⚠ textBaselinectx.textBaseline = "top" antes de cada fillText(). Sin esto el texto se corta en la parte superior.
MÉTODODESCRIPCIÓN
crear(virtualCanvas)Constructor. El canvas virtual es el destino exclusivo del intérprete.
limpiar()fillRect completo con colorFondo.
punto(x,y)fillRect(x,y,1,1). Color de trazo.
linea(coords[], cerrar, relleno)beginPath/moveTo/lineTo. N puntos. cerrar cierra el path.
circulo(x,y,r,relleno)arc().
rectangulo(x,y,ancho,alto,relleno)ancho antes que alto.
triangulo(x1,y1,x2,y2,x3,y3,relleno)Path de 3 vértices.
poligono(x,y,r,lados,relleno)Cálculo trigonométrico + path.
texto(x,y,str,sombreado)fillText. textBaseline="top" obligatorio.
textoMD(x,y,ancho,str,alto?)Pipeline markdown. alto = 5° arg opcional.
alturaMD(ancho,str)Calcula altura sin dibujar. Retorna number.
rellenar(x,y)Flood fill por scanline sobre ImageData.
setColor / setColorTrazo / setColorRelleno / setColorFondoEstado de color.
setGrosor(px)lineWidth.

9. Runtime Input

📌 CONVENCIÓN DE TECLASLas constantes TECLA_A..Z y TECLA_0..9 NO existen en QBJr. Las teclas de letra/número se referencian con el carácter entre comillas: TECLADO("W"), TECLADO("5"). Solo las teclas especiales tienen constantes nombradas.
MÉTODODESCRIPCIÓN
crear(displayCanvas, vw, vh)Constructor con canvas visible y dimensiones virtuales para conversión de coordenadas.
attach(window)Conectar listeners de teclado, mouse y touch.
detach()Desconectar todos los listeners.
frameReset()Limpiar estados de frame: click, suelta, ruedaArriba, ruedaAbajo. Llamar al inicio de cada frame, ANTES de gen.next().
teclado(name)VERDADERO mientras la tecla está presionada.
keysPressed(keys[])Combo AND — VERDADERO si todas las teclas están presionadas.
botonMouse(action)BOTON_MOUSE() — click/mantiene/suelta para izquierdo/centro/derecho.
posicionMouse(axis)POSICION_MOUSE(x/y) — coordenadas en espacio virtual.
ruedaMouse(dir)RUEDA_MOUSE(arriba/abajo) — VERDADERO si se movió en este frame.
mouseEncima(sprite)MOUSE_ENCIMA() — hover sobre hitbox del sprite.

10. Runtime Sprites

MÉTODODESCRIPCIÓN
crearSprite(nombre, img?)CREAR SPRITE. img es URL o null para sprites con animaciones.
mostrar(sprite, inst, x, y)MOSTRAR. Crea la instancia si no existe.
ocultar(sprite, inst?)OCULTAR. Sin inst: oculta todas.
borrar(sprite)BORRAR. Destruye sprite e instancias.
mover(sprite, inst, x, y, soloHitbox)MOVER / MOVER HITBOX.
rotar(sprite, inst, grados, soloHitbox)ROTAR / ROTAR HITBOX.
escalar(sprite, inst, factor, soloHitbox)ESCALAR / ESCALAR HITBOX.
voltear(sprite, inst, horizontal)VOLTEAR HORIZONTAL/VERTICAL.
crearAnimacion(nom, sprite, frames[])CREAR ANIMACION.
reproducirAnim(anim, sprite, inst, loop, n)REPRODUCIR animación.
tick(ctx, dt)Llamar cada frame. Avanza animaciones y dibuja sprites.
colisiona(sprite, inst?)COLISIONA() unario — AABB vs todos los sprites.
colisionaEntre(spriteA, spriteB)A COLISIONA B infijo — AABB entre dos sprites.
colisionaTile(aabb)COLISIONA_TILE() — vs tiles rígidos activos.

11. Runtime Audio

MÉTODODESCRIPCIÓN
crearSonido(nombre, url)CREAR SONIDO — SFX via fetch + decodeAudioData.
crearMusica(nombre, url)CREAR MUSICA — BGM, loop por defecto.
reproducir(nombre, loop, vol, paneo)REPRODUCIR. AudioBufferSourceNode + GainNode + StereoPannerNode.
detener(nombre, fadeMs?)DETENER con fundido opcional via GainNode ramp.
setVolumen(nombre, 0-100)VOLUMEN. silencio=0 bajo=25 medio=50 alto=75 maximo=100.
setPaneo(nombre, -50a50)PANEO. izquierda=-50 centro=0 derecha=50.
crearPista(nombre, secuencia)CREAR PISTA — síntesis por OscillatorNode.
reproducirPista(nombre, loop, canal)REPRODUCIR pista en canal.
setTempo(bpm)TEMPO — negra_ms = 60000 / BPM.
📌 AUTOPLAYAudioContext debe inicializarse de forma lazy — el primer sonido se activa en respuesta a un gesto del usuario. El browser bloquea el audio hasta entonces.

12. Runtime Files

⚠ reset() en stop()Si filesRT.reset() no se llama en engine.stop(), los datasets de la sesión anterior persisten en memoria. Al reiniciar, CARGAR_DATOS puede encontrar datos residuales.
MÉTODODESCRIPCIÓN
abrir(ruta, nombre, modo)ABRIR. LECTURA: fetch del servidor. ESCRITURA/ANEXAR: localStorage.
leer(nombre, varCallback)LEER — siguiente línea del archivo.
escribir(nombre, texto)ESCRIBIR — una línea.
cerrar(nombre)CERRAR y persistir en localStorage si era ESCRITURA.
finArchivo(nombre)FIN_ARCHIVO() — VERDADERO si no hay más líneas.
cargarDatos(ruta, nombre)CARGAR_DATOS — parsea .qdat y almacena en memoria.
leerDato(datos, seccion, clave)LEER_DATO() — usa argStr() para la clave, no argNom().
reset()Limpiar todos los datasets en memoria. Llamar en engine.stop().

13. Engine

MÉTODODESCRIPCIÓN
crear({virtualCanvas, displayCanvas, onError, onEnd, onLog})Factory principal.
cargar(fuenteQBJr)Lexea + parsea. No ejecuta. Retorna lista de errores de parseo.
play()Inicia el game loop (RAF). Si estaba pausado, reanuda.
pause()Cancela el RAF sin destruir el generator. play() retoma exactamente donde estaba.
stop()Termina el programa, destruye el generator, llama filesRT.reset().
clearPause()Limpia el flag de PAUSAR programático. Llamar al presionar START.
getState()Retorna: idle | running | paused | stopped.
⚠ TWO-CANVASSi virtualCanvas === displayCanvas, copiarADisplay() hace drawImage(canvas, canvas). En Chrome/WebKit es un no-op. La pantalla queda negra sin error. Verificar al inicio: if (virtual === display) throw Error("canvas must be different objects").

14. Funciones de cadena avanzadas

Incorporadas en v0.9.8 a partir del desarrollo de horror.qbjr. Todas indexadas base-1 (el primer carácter es posición 1), coherentes con QBasic.

FUNCIÓNDESCRIPCIÓNEQUIVALENTE QB
BUSCAR(texto, aguja [, desde])Posición 1-based de la primera ocurrencia. 0 si no encuentra.INSTR
EXTRAER(texto, desde [, largo])Subcadena desde la posición, de largo caracteres.MID$
CORTAR_IZQ(texto, n)Primeros n caracteres.LEFT$
CORTAR_DER(texto, n)Últimos n caracteres.RIGHT$
REEMPLAZAR(texto, viejo, nuevo)Reemplaza todas las ocurrencias.
REPETIR(texto, n)Repite el texto n veces.STRING$
CARACTER(n)Carácter por código Unicode.CHR$
CODIGO(texto)Código Unicode del primer carácter.ASC
📌La implementación de referencia hace la conversión interna de base-1 a base-0 de JavaScript. En otros lenguajes donde los strings ya son base-1 (Pascal, VBA), no es necesaria la conversión.

15. Trampas conocidas para implementadores

Estas son las divergencias entre el diseño original y la implementación real que más tiempo costaron. Cada una tiene un síntoma concreto y una solución probada.

T-01
Two-canvas: mismo objeto para virtual y display
🔴 PROBLEMASi virtualCanvas === displayCanvas, copiarADisplay() hace drawImage(canvas, canvas) sobre sí mismo. En Chrome/WebKit es un no-op. La pantalla queda negra sin error visible.
✅ SOLUCIÓNUsar dos elementos <canvas> distintos en el DOM. Assertion al inicio: if (virtual === display) throw Error("canvas must be different objects").
T-02
RECTANGULO: args al revés
🔴 PROBLEMAImplementar rectangulo(x,y,alto,ancho) en lugar de (x,y,ancho,alto) hace que todas las barras horizontales aparezcan verticales. El bug es invisible con rectángulos cuadrados.
✅ SOLUCIÓNTest inmediato obligatorio: RECTANGULO 10 10 100 20 RELLENO debe ser una barra horizontal ancha (100px ancho, 20px alto).
T-03
dt calculado después de lastFrame
🔴 PROBLEMAInvertir el orden: lastFrame = now; const _dt = now - lastFrame; produce _dt = 0 siempre. La física queda estática y las animaciones no avanzan.
✅ SOLUCIÓNSiempre: const _dt = now - lastFrame; ANTES de lastFrame = now;
T-04
argNom() para LEER_DATO
🔴 PROBLEMACon argNom(), LEER_DATO(mapa salaKey n) retorna siempre el literal "salaKey" como sección, ignorando que es una variable con valor "sala_3". Todas las salas fallan silenciosamente.
✅ SOLUCIÓNUsar argStr() para LEER_DATO. Semántica híbrida: variable string no vacía → su valor; TODO_MAYÚSCULAS → lowercase; mixto no-variable → literal.
T-05
Nombre de variable igual a clave de .qdat
🔴 PROBLEMAargStr() usa el valor de la variable si existe. Si la variable archivo ya tiene valor "SALA_VESTIBULO.qdat" de la sala anterior, LEER_DATO(mapa salaKey archivo) busca "SALA_VESTIBULO.qdat" como clave en vez de "archivo".
✅ SOLUCIÓNNombrar la variable archivoSala en vez de archivo. Documentar esta restricción: los nombres de variables no deben coincidir con claves del .qdat que se va a leer.
T-06
TECLA_A..Z removidas
🔴 PROBLEMALas constantes TECLA_A, TECLA_Z, TECLA_0..9 no existen en QBJr. El handler de KeyCombo que las incluya viola la spec pedagógica del autor.
✅ SOLUCIÓNMapear caracteres a KeyboardEvent.code: "W"→"KeyW", "5"→"Digit5". El programador usa TECLADO("W"), TECLADO("5"). Solo las teclas especiales tienen constantes (FLECHA_*, BARRA_ESPACIADORA, etc.).
T-07
SI NO (else) silencioso
🔴 PROBLEMA"SI NO" en el stream parece el inicio de "SI NOT condicion". parseBlock() procesando el cuerpo de un OTRO SI no sabe detenerse. El bloque else queda vacío sin error de parseo.
✅ SOLUCIÓNEn parseBlock(): si token actual es SI y siguiente es NO, detener sin consumir. Luego parsear SI+NO como bloque else.

16. Mapa de portabilidad

El núcleo del intérprete (Lexer, Parser, Intérprete) puede correr en cualquier entorno con JavaScript (Node.js, Deno, QuickJS). Los runtimes son la única capa que necesita reimplementarse por plataforma.

COMPONENTEJS (REFERENCIA)PLATAFORMA NATIVA
Lexerqbjr-lexer.jsReescribir. El mapa de KEYWORDS es el contrato.
Parserqbjr-parser.jsReescribir. La estructura del AST es el contrato.
Intérpreteqbjr-interpreter.jsGenerators → C++20 coroutines / C# async/await / Python generators.
Canvas RTCanvas 2D APISDL_Renderer / Godot CanvasItem / SFML / OpenGL.
Input RTKeyboardEvent + TouchSDL_Event / Godot Input / Win32 WM_KEY*.
Sprites RTCanvas + ImageSDL_Texture / Godot Sprite2D / SFML Sprite.
Audio RTWeb Audio APISDL_mixer / miniaudio / Godot AudioStreamPlayer.
Layers RTOffscreenCanvasRender textures / Godot CanvasLayer.
Tiles RTOffscreenCanvasTilemap del engine destino.
Physics RTJS puroReimplementar o usar motor nativo con contrato equivalente.
Files RTfetch + localStoragefopen/fclose / File API del SO.
Text RTCanvas 2D fillTextCualquier renderer con measureText equivalente.
EnginerequestAnimationFrameSDL game loop / Godot _process() / Unity Update().

17. Orden de implementación recomendado

ORDENMÓDULONOTAS
1Lexer + ParserSin runtime. Verificar AST correcto con los tests de la §18.
2Canvas RT (básico)Verificar RECTANGULO con barra horizontal inmediatamente.
3Input RTImprescindible para cualquier programa interactivo.
4EngineVerificar two-canvas y dt antes de lastFrame antes de avanzar.
5Sprites RTDepende de Canvas e Input.
6Audio RTIndependiente. Cuidado con el requisito de gesto de usuario.
7Layers RTIndependiente.
8Tiles RTDepende de Layers.
9Physics RTDepende de Sprites y Tiles. Verificar el orden del tick.
10Files RTAsincrónico. Requiere yieldPromise o equivalente.
11Text RTDepende de Canvas (fillText, measureText).

18. Suite de tests mínima

Estos tests deben pasar antes de conectar el intérprete a ningún runtime. Cubren los casos más propensos a errores de implementación.

' Test 1: variables de una letra — deben ser IDENTIFIER, no operadores x = 5 y = 10 r = 3 IMPRIMIR x + y + r ' debe imprimir 18 ' Test 2: AND OR NOT en mayúsculas solo; minúsculas son variables DECLARAR and COMO BOOLEANO and = VERDADERO SI and AND NOT FALSO ENTONCES IMPRIMIR "ok" FIN SI ' debe imprimir "ok" ' Test 3: precedencia — multiplicación antes que suma x = 2 + 3 * 4 IMPRIMIR x ' debe ser 14, no 20 ' Test 4: SI NO (else) SI FALSO ENTONCES IMPRIMIR "mal" SI NO IMPRIMIR "bien" ' debe imprimir esto FIN SI ' Test 5: subprogramas con variables globales x = 5 DEFINIR doble x = x * 2 FIN DEFINIR HACER doble HACER doble IMPRIMIR x ' debe ser 20 ' Test 6: arreglos base-1 DECLARAR arr[3] COMO ENTERO arr[1] = 10 arr[2] = 20 arr[3] = 30 IMPRIMIR arr[2] ' debe ser 20 ' Test 7: PARA con acumulador s = 0 PARA i DE 1 A 10 s = s + i FIN PARA IMPRIMIR s ' debe ser 55 ' Test 8: SEGUN / CASO OTRO x = 99 SEGUN x CASO 1: IMPRIMIR "uno" CASO OTRO: IMPRIMIR "otro" ' debe imprimir esto FIN SEGUN