@getparallel/parx 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { carpetaDeConfiguracion } from "./config.js";
4
+ import { MODELOS, ORDEN_DE_MODELOS, nombreDeModelo } from "./modelos.js";
5
+ import { t } from "./textos.js";
6
+ export const PERFILES = {
7
+ pregunta: { vueltas: 2, crecimiento: 700, salidaPorVuelta: 80, salidaFinal: 350 },
8
+ pequena: { vueltas: 4, crecimiento: 900, salidaPorVuelta: 350, salidaFinal: 200 },
9
+ media: { vueltas: 8, crecimiento: 1200, salidaPorVuelta: 550, salidaFinal: 250 },
10
+ grande: { vueltas: 16, crecimiento: 1500, salidaPorVuelta: 650, salidaFinal: 350 },
11
+ };
12
+ const GRANDE = /\b(refactor|refactoriz|migrat|migra[r]?\b|rewrite|reescrib|rework|whole (project|app|codebase|repo)|entire|todo el (proyecto|código|repo)|toda la (app|aplicación)|all (the )?files|todos los ficheros|architecture|arquitectura|from scratch|desde cero|full[- ]stack|authentication|autenticaci|database|base de datos|deploy|despliega|integrat|integra[r]?\b|test suite|end[- ]to[- ]end|e2e|multi[- ]?page|varias páginas|dashboard|backend|api rest|microservic)/i;
13
+ const CREA = /\b(create|build|make|write|generate|implement|crea[r]?|construye|monta|escribe|genera|implementa|haz(me)?|new|nuev[oa])\b/i;
14
+ const SUSTANCIAL = /\b(page|página|pagina|website|web ?site|site|web|app|form|formulario|component|componente|feature|funcionalidad|endpoint|screen|pantalla|module|módulo|modulo|api|script|server|servidor|service|servicio|table|tabla|model|modelo|view|vista)\b/i;
15
+ const PEQUENA = /\b(typo|errata|rename|renombra|one[- ]line|una línea|small|pequeñ|minor|mínim|quick|rápid|simple|sencill|tiny|just|solo|sólo|only|fix the|arregla el|arregla la|change the|cambia el|cambia la|add a |añade un|añade una|remove the|quita el|quita la|update the|actualiza el)\b/i;
16
+ const PREGUNTA = /^(what|why|how|where|which|who|when|is|are|does|do|can|could|should|explain|describe|tell me|show me|list|qué|que|por qué|cómo|como|dónde|donde|cuál|cual|quién|quien|cuándo|explica|describe|dime|enséñame|lista|hay)\b/i;
17
+ function palabras(s) {
18
+ return s.trim().split(/\s+/).filter(Boolean).length;
19
+ }
20
+ export function clasificarTarea(pedido) {
21
+ const s = pedido.replace(/\s+/g, ' ').trim();
22
+ const n = palabras(s);
23
+ if (!s)
24
+ return 'pequena';
25
+ if (GRANDE.test(s) || n > 90)
26
+ return 'grande';
27
+ const pregunta = PREGUNTA.test(s) || (s.endsWith('?') && !CREA.test(s));
28
+ if (pregunta && !CREA.test(s))
29
+ return 'pregunta';
30
+ const creaAlgo = CREA.test(s) || SUSTANCIAL.test(s);
31
+ if (n <= 10 && !creaAlgo)
32
+ return 'pequena';
33
+ if (PEQUENA.test(s) && !creaAlgo)
34
+ return 'pequena';
35
+ return 'media';
36
+ }
37
+ export function tokensDe(texto) {
38
+ const chars = typeof texto === 'number' ? texto : texto.length;
39
+ return Math.ceil(chars / 3.8);
40
+ }
41
+ export function accionesEnBruto(clase, baseTokens, tarifa) {
42
+ const p = PERFILES[clase];
43
+ let entrada = 0;
44
+ let salida = 0;
45
+ for (let i = 0; i < p.vueltas; i++) {
46
+ entrada += baseTokens + i * p.crecimiento;
47
+ salida += i === p.vueltas - 1 ? p.salidaFinal : p.salidaPorVuelta;
48
+ }
49
+ return (entrada * tarifa.entrada + salida * tarifa.salida) / 1000;
50
+ }
51
+ export function redondear(n) {
52
+ if (n < 1)
53
+ return 1;
54
+ if (n < 10)
55
+ return Math.round(n);
56
+ const magnitud = 10 ** (Math.floor(Math.log10(n)) - 1);
57
+ return Math.round(n / magnitud) * magnitud;
58
+ }
59
+ export function estimarTarea(e) {
60
+ const clase = clasificarTarea(e.pedido);
61
+ const base = tokensDe(e.contextoChars) + tokensDe(e.pedido);
62
+ const acciones = {};
63
+ for (const m of ORDEN_DE_MODELOS) {
64
+ const bruto = accionesEnBruto(clase, base, e.tarifas[MODELOS[m].servidor]);
65
+ acciones[m] = redondear(bruto * factorDeCalibracion(e.calibracion, clase, m));
66
+ }
67
+ return { clase, acciones };
68
+ }
69
+ export function lineaDeEstimacion(est, modelo) {
70
+ const actual = est.acciones[modelo];
71
+ let sugerido = null;
72
+ if (modelo === 'summit' && est.clase !== 'grande')
73
+ sugerido = 'stride';
74
+ if ((modelo === 'summit' || modelo === 'stride') && (est.clase === 'pregunta' || est.clase === 'pequena'))
75
+ sugerido = 'swift';
76
+ if (sugerido && est.acciones[sugerido] <= actual * 0.6 && actual >= 5) {
77
+ return t('estimacion.con', { n: actual.toLocaleString('en-US'), modelo: nombreDeModelo(sugerido).replace(/ \d+(\.\d+)?$/, ''), otro: est.acciones[sugerido].toLocaleString('en-US') });
78
+ }
79
+ return t('estimacion.linea', { n: actual.toLocaleString('en-US') });
80
+ }
81
+ const FICHERO = 'estimaciones.json';
82
+ const PESO = 0.3;
83
+ export function leerCalibracion(dir = carpetaDeConfiguracion()) {
84
+ try {
85
+ const c = JSON.parse(readFileSync(join(dir, FICHERO), 'utf8'));
86
+ return c && typeof c === 'object' ? c : {};
87
+ }
88
+ catch {
89
+ return {};
90
+ }
91
+ }
92
+ function guardarCalibracion(c, dir) {
93
+ try {
94
+ if (!existsSync(dir))
95
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
96
+ writeFileSync(join(dir, FICHERO), JSON.stringify(c, null, 2), { mode: 0o600 });
97
+ }
98
+ catch {
99
+ }
100
+ }
101
+ export function factorDeCalibracion(c, clase, modelo) {
102
+ const f = c?.[clase]?.[modelo]?.factor;
103
+ if (typeof f !== 'number' || !Number.isFinite(f) || f <= 0)
104
+ return 1;
105
+ return Math.min(2, Math.max(0.5, f));
106
+ }
107
+ export function aprenderDeLaTarea(o) {
108
+ const dir = o.dir ?? carpetaDeConfiguracion();
109
+ const c = o.calibracion ?? leerCalibracion(dir);
110
+ if (!(o.estimada > 0) || !(o.real > 0))
111
+ return c;
112
+ const ratio = Math.min(4, Math.max(0.25, o.real / o.estimada));
113
+ const antes = c[o.clase]?.[o.modelo];
114
+ const factor = antes ? antes.factor * (1 - PESO) + ratio * PESO : ratio;
115
+ c[o.clase] = { ...(c[o.clase] ?? {}), [o.modelo]: { factor: Number(factor.toFixed(3)), n: (antes?.n ?? 0) + 1 } };
116
+ guardarCalibracion(c, dir);
117
+ return c;
118
+ }
package/dist/firmas.js ADDED
@@ -0,0 +1,55 @@
1
+ import { createHash, createVerify } from 'node:crypto';
2
+ export const CLAVES_DE_NPM = [
3
+ {
4
+ keyid: 'SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U',
5
+ key: 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY6Ya7W++7aUPzvMTrezH6Ycx3c+HOKYCcNGybJZSCJq/fd7Qa8uuAKtdIkUQtQiEKERhAmE5lMMJhP8OkDOa2g==',
6
+ },
7
+ ];
8
+ const REGISTRO_DE_NPM = 'https://registry.npmjs.org';
9
+ export function keyidDe(spkiBase64) {
10
+ return `SHA256:${createHash('sha256').update(Buffer.from(spkiBase64, 'base64')).digest('base64').replace(/=+$/, '')}`;
11
+ }
12
+ export function clavesDeConfianza(env = process.env) {
13
+ const claves = [...CLAVES_DE_NPM];
14
+ const registro = (env.PARX_REGISTRY ?? '').replace(/\/+$/, '');
15
+ if (registro && registro !== REGISTRO_DE_NPM && env.PARX_CLAVES_DE_FIRMA) {
16
+ for (const k of env.PARX_CLAVES_DE_FIRMA.split(',').map((x) => x.trim()).filter(Boolean))
17
+ claves.push({ keyid: keyidDe(k), key: k });
18
+ }
19
+ return claves;
20
+ }
21
+ export function mensajeFirmado(nombre, version, integrity) {
22
+ return `${nombre}@${version}:${integrity}`;
23
+ }
24
+ export function firmaValida(d, claves = CLAVES_DE_NPM) {
25
+ if (!Array.isArray(d.firmas) || !d.firmas.length)
26
+ return false;
27
+ if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(d.integrity))
28
+ return false;
29
+ const mensaje = mensajeFirmado(d.nombre, d.version, d.integrity);
30
+ for (const f of d.firmas) {
31
+ if (!f || typeof f !== 'object')
32
+ continue;
33
+ const { keyid, sig } = f;
34
+ if (typeof keyid !== 'string' || typeof sig !== 'string')
35
+ continue;
36
+ const clave = claves.find((k) => k.keyid === keyid);
37
+ if (!clave)
38
+ continue;
39
+ try {
40
+ const v = createVerify('SHA256');
41
+ v.update(mensaje);
42
+ if (v.verify(`-----BEGIN PUBLIC KEY-----\n${clave.key}\n-----END PUBLIC KEY-----`, sig, 'base64'))
43
+ return true;
44
+ }
45
+ catch {
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ export function integridadDe(contenido) {
51
+ return `sha512-${createHash('sha512').update(contenido).digest('base64')}`;
52
+ }
53
+ export function integridadCoincide(contenido, integrity) {
54
+ return integridadDe(contenido) === integrity;
55
+ }
package/dist/marca.js CHANGED
@@ -125,27 +125,64 @@ export function logo() {
125
125
  const tonos = [c.marca, c.claro, c.claro, c.violeta];
126
126
  return filas.map((f, i) => tonos[i](f));
127
127
  }
128
- export const ANCHO_DE_PAR = 6;
129
- export function cabezaDePar() {
130
- return caps.unicode ? c.claro('▗▄▄▄▄▖') : c.claro(' .--. ');
128
+ export const ANCHO_DE_PAR = 8;
129
+ function miniPar(gesto) {
130
+ const ojo = gesto === 'error' ? 'R' : 'E';
131
+ const fila1 = ['.', '.', 'B', 'B', '.', 'B', 'B', '.'];
132
+ if (gesto === 'pregunta') {
133
+ fila1.splice(0, 8, '.', '.', ojo, ojo, '.', ojo, ojo, '.');
134
+ }
135
+ else if (gesto !== 'parpadeo') {
136
+ const izquierda = gesto === 1 || gesto === 2;
137
+ const a = izquierda ? 2 : 3;
138
+ fila1[a] = ojo;
139
+ fila1[a + 3] = ojo;
140
+ }
141
+ return [
142
+ ['.', '.', '.', 'B', 'B', '.', 'B', 'B'],
143
+ fila1,
144
+ ['.', 'B', 'B', '.', 'B', 'B', '.', '.'],
145
+ ['B', 'B', '.', 'B', 'B', '.', '.', '.'],
146
+ ];
131
147
  }
132
- export function caraDePar(gesto = 'quieto') {
133
- const u = caps.unicode;
134
- const ojos = gesto === 'parpadeo'
135
- ? u ? ' ── ' : ' -- '
136
- : gesto === 'pregunta'
137
- ? u ? ' •• ' : ' oo '
138
- : gesto === 'error'
139
- ? u ? ' ×× ' : ' xx '
140
- : typeof gesto === 'number'
141
- ?
142
- (u ? ['╱╱ ', ' ╱╱ ', ' ╱╱', ' ╱╱ '] : ['// ', ' // ', ' //', ' // '])[gesto]
143
- : u ? ' ╱╱ ' : ' // ';
144
- if (caps.color) {
145
- const fondo = caps.color === 3 ? '\x1b[48;2;139;112;255m' : caps.color === 2 ? '\x1b[48;5;141m' : '\x1b[45m';
146
- return `${fondo}\x1b[1;97m ${ojos} \x1b[22;39;49m`;
148
+ const RGB_DE_PAR = { B: [139, 112, 255], E: [246, 243, 255], R: [255, 107, 107] };
149
+ const C256_DE_PAR = { B: 141, E: 255, R: 203 };
150
+ function filaDePar(arriba, abajo) {
151
+ let out = '';
152
+ for (let x = 0; x < ANCHO_DE_PAR; x++) {
153
+ const a = arriba[x];
154
+ const b = abajo[x];
155
+ if (!caps.unicode || !caps.color) {
156
+ const hay = a !== '.' || b !== '.';
157
+ const ojo = a === 'E' || a === 'R' || b === 'E' || b === 'R';
158
+ out += ojo ? 'o' : hay ? (caps.unicode ? '█' : '/') : ' ';
159
+ continue;
160
+ }
161
+ if (a === '.' && b === '.') {
162
+ out += ' ';
163
+ continue;
164
+ }
165
+ const fg = a === '.' ? null : a;
166
+ const bg = b === '.' ? null : b;
167
+ const color = (p, fondo) => caps.color === 3 ? `\x1b[${fondo ? 48 : 38};2;${RGB_DE_PAR[p].join(';')}m` : caps.color === 2 ? `\x1b[${fondo ? 48 : 38};5;${C256_DE_PAR[p]}m` : p === 'B' ? `\x1b[${fondo ? 45 : 35}m` : p === 'R' ? `\x1b[${fondo ? 41 : 31}m` : `\x1b[${fondo ? 47 : 97}m`;
168
+ if (fg && bg && fg === bg)
169
+ out += `${color(fg, false)}█\x1b[39m`;
170
+ else if (fg && bg)
171
+ out += `${color(fg, false)}${color(bg, true)}▀\x1b[39;49m`;
172
+ else if (fg)
173
+ out += `${color(fg, false)}▀\x1b[39m`;
174
+ else
175
+ out += `${color(bg, false)}▄\x1b[39m`;
147
176
  }
148
- return u ? `▐${ojos}▌` : `[${ojos}]`;
177
+ return out;
178
+ }
179
+ export function cabezaDePar(gesto = 'quieto') {
180
+ const p = miniPar(gesto);
181
+ return filaDePar(p[0], p[1]);
182
+ }
183
+ export function caraDePar(gesto = 'quieto') {
184
+ const p = miniPar(gesto);
185
+ return filaDePar(p[2], p[3]);
149
186
  }
150
187
  export const ANCHO_DE_ANIMACION = 8;
151
188
  export function animacion(tipo, paso) {
package/dist/par.js ADDED
@@ -0,0 +1,355 @@
1
+ export const LADO = 16;
2
+ export const TONOS = {
3
+ 300: { cuerpo: '#ae9bff', sombra: '#8b70ff', detalle: '#ddd3ff' },
4
+ 400: { cuerpo: '#8b70ff', sombra: '#6d4aff', detalle: '#ae9bff' },
5
+ 500: { cuerpo: '#7b5cff', sombra: '#5a36e6', detalle: '#ae9bff' },
6
+ 600: { cuerpo: '#6d4aff', sombra: '#5a36e6', detalle: '#ae9bff' },
7
+ 700: { cuerpo: '#5a36e6', sombra: '#4626ba', detalle: '#8b70ff' },
8
+ };
9
+ export const ORDEN_DE_TONOS = [600, 400, 700, 500, 300];
10
+ export const TINTA = '#15131f';
11
+ export const CLARO = '#d3cde8';
12
+ export const ORO = '#f2b632';
13
+ function huella(s) {
14
+ let h = 2166136261;
15
+ for (let i = 0; i < s.length; i++) {
16
+ h ^= s.charCodeAt(i);
17
+ h = Math.imul(h, 16777619);
18
+ }
19
+ return h >>> 0;
20
+ }
21
+ export function tonoDe(semilla) {
22
+ if (!semilla)
23
+ return 600;
24
+ return ORDEN_DE_TONOS[huella(semilla) % ORDEN_DE_TONOS.length];
25
+ }
26
+ export function tonosDelEquipo(semillas) {
27
+ const usados = new Set();
28
+ return semillas.map((s) => {
29
+ let t = tonoDe(s);
30
+ if (usados.size < ORDEN_DE_TONOS.length) {
31
+ let i = ORDEN_DE_TONOS.indexOf(t);
32
+ while (usados.has(t)) {
33
+ i = (i + 1) % ORDEN_DE_TONOS.length;
34
+ t = ORDEN_DE_TONOS[i];
35
+ }
36
+ }
37
+ usados.add(t);
38
+ return t;
39
+ });
40
+ }
41
+ export function colorDe(c, tono) {
42
+ const t = TONOS[tono];
43
+ switch (c) {
44
+ case 'B':
45
+ return t.cuerpo;
46
+ case 'D':
47
+ return t.sombra;
48
+ case 'A':
49
+ return t.detalle;
50
+ case 'E':
51
+ return TINTA;
52
+ case 'W':
53
+ return CLARO;
54
+ case 'Y':
55
+ return ORO;
56
+ default:
57
+ return null;
58
+ }
59
+ }
60
+ const INICIO = [7, 11];
61
+ const ARRIBA = 2;
62
+ const FILAS = 12;
63
+ const FILA_OJO = ARRIBA + 3;
64
+ function pon(m, x, y, c) {
65
+ if (x >= 0 && x < LADO && y >= 0 && y < LADO)
66
+ m[y][x] = c;
67
+ }
68
+ export function matrizDe(c, accesorio) {
69
+ const m = Array.from({ length: LADO }, () => Array(LADO).fill('.'));
70
+ const ojos = Array.isArray(c.ojos) ? c.ojos : [c.ojos ?? 'abierto', c.ojos ?? 'abierto'];
71
+ [c.izq, c.der].forEach((b, k) => {
72
+ const dx = b?.dx ?? 0;
73
+ const dy = b?.dy ?? 0;
74
+ const estira = b?.estira ?? 0;
75
+ const inicio = INICIO[k];
76
+ const xDe = (y) => inicio - Math.floor((y - ARRIBA) / 2) + dx;
77
+ for (let y = ARRIBA - estira; y <= ARRIBA + FILAS; y++) {
78
+ const color = y === ARRIBA + FILAS ? 'D' : 'B';
79
+ for (let i = 0; i < 3; i++)
80
+ pon(m, xDe(y) + i, y + dy, color);
81
+ }
82
+ const filaOjo = FILA_OJO - estira;
83
+ const fy = filaOjo + dy;
84
+ const cx = xDe(filaOjo) + 1;
85
+ const ojo = ojos[k];
86
+ switch (ojo) {
87
+ case 'abierto':
88
+ pon(m, cx, fy, 'E');
89
+ break;
90
+ case 'cerrado':
91
+ pon(m, cx - 1, fy, 'D');
92
+ pon(m, cx, fy, 'D');
93
+ pon(m, cx + 1, fy, 'D');
94
+ break;
95
+ case 'feliz':
96
+ pon(m, cx, fy - 1, 'E');
97
+ pon(m, cx - 1, fy, 'E');
98
+ pon(m, cx + 1, fy, 'E');
99
+ break;
100
+ case 'grande':
101
+ pon(m, cx, fy - 1, 'E');
102
+ pon(m, cx, fy, 'E');
103
+ break;
104
+ case 'x':
105
+ pon(m, cx - 1, fy - 1, 'E');
106
+ pon(m, cx + 1, fy - 1, 'E');
107
+ pon(m, cx, fy, 'E');
108
+ pon(m, cx - 1, fy + 1, 'E');
109
+ pon(m, cx + 1, fy + 1, 'E');
110
+ break;
111
+ case 'abajo':
112
+ pon(m, cx, fy + 1, 'E');
113
+ break;
114
+ case 'arriba':
115
+ pon(m, cx, fy - 1, 'E');
116
+ break;
117
+ case 'izquierda':
118
+ pon(m, cx - 1, fy, 'E');
119
+ break;
120
+ case 'derecha':
121
+ pon(m, cx + 1, fy, 'E');
122
+ break;
123
+ }
124
+ });
125
+ if (accesorio) {
126
+ for (const [x, y, ch] of PIEZAS[accesorio]) {
127
+ const b = x <= 9 ? c.izq : c.der;
128
+ pon(m, x + (b?.dx ?? 0), y + (b?.dy ?? 0) - (b?.estira ?? 0), ch);
129
+ }
130
+ }
131
+ for (const p of c.extra ?? [])
132
+ pon(m, p[0], p[1], p[2] ?? 'A');
133
+ return m.map((f) => f.join(''));
134
+ }
135
+ const FILAS_DE_ACCESORIOS = {
136
+ gorra: { 0: '...........DDD..', 1: '...........DDDDD' },
137
+ copa: { 0: '.......EAE......', 1: '......EEEEE.....' },
138
+ astronauta: { 0: '........WWWWW...', 1: '......WW.....WW.', 2: '......W.......W.', 3: '......W.......W.' },
139
+ corona: { 0: '...........Y.Y..', 1: '...........YYY..' },
140
+ gafas_sol: { 4: '......WEE.WEE...', 5: '......EEEEEEE...' },
141
+ gafas: { 4: '......YYY.YYY...', 5: '......Y.YYY.Y...', 6: '......YYY.YYY...' },
142
+ bigote: { 7: '.......EEE......' },
143
+ pajarita: { 9: '.....EEAEE......' },
144
+ idea: { 0: '.......YYY......', 1: '......A.Y.A.....' },
145
+ gato: { 0: '...........B.B..', 1: '...........BBB..' },
146
+ fiesta: { 0: '........Y.......', 1: '.......YEY......' },
147
+ auriculares: { 0: '............E...', 1: '...........E.E..', 3: '..........E....E', 4: '..........E....E' },
148
+ cocinero: { 0: '......WWWWW.....', 1: '.......WWW......' },
149
+ pirata: { 4: '......EEE.......', 5: '......EEE.......' },
150
+ bufanda: { 10: '...YYY.YYY......', 11: '........YY......' },
151
+ antena: { 0: '............Y...', 1: '............D...' },
152
+ sombrilla: { 0: '.......DDD......', 1: '......DDDDD.....' },
153
+ nube: { 0: '.......WW.......', 1: '......WWWW......', 2: '.....A..........', 3: '....A...........', 4: '.....A..........' },
154
+ halo: { 0: '.......YYY......' },
155
+ pelo: { 0: '......B.B.B.....', 1: '......BBBBB.....' },
156
+ };
157
+ export const LISTA_DE_ACCESORIOS = Object.keys(FILAS_DE_ACCESORIOS);
158
+ const PIEZAS = Object.fromEntries(LISTA_DE_ACCESORIOS.map((id) => [
159
+ id,
160
+ Object.entries(FILAS_DE_ACCESORIOS[id]).flatMap(([y, fila]) => [...fila].flatMap((ch, x) => (ch === '.' ? [] : [[x, Number(y), ch]]))),
161
+ ]));
162
+ export function accesorioDe(semilla) {
163
+ if (!semilla)
164
+ return null;
165
+ return LISTA_DE_ACCESORIOS[huella(`${semilla}·accesorio`) % LISTA_DE_ACCESORIOS.length];
166
+ }
167
+ export function accesoriosDelEquipo(semillas) {
168
+ const usados = new Set();
169
+ return semillas.map((s) => {
170
+ let a = accesorioDe(s || 'parallel');
171
+ if (usados.size < LISTA_DE_ACCESORIOS.length) {
172
+ let i = LISTA_DE_ACCESORIOS.indexOf(a);
173
+ while (usados.has(a)) {
174
+ i = (i + 1) % LISTA_DE_ACCESORIOS.length;
175
+ a = LISTA_DE_ACCESORIOS[i];
176
+ }
177
+ }
178
+ usados.add(a);
179
+ return a;
180
+ });
181
+ }
182
+ const P = (puntos, dx = 0, dy = 0, c = 'A') => puntos.map(([x, y]) => [x + dx, y + dy, c]);
183
+ const INTERROGACION = [[1, 0], [2, 0], [3, 1], [2, 2], [2, 4]];
184
+ const EXCLAMACION = [[2, 0], [2, 1], [2, 2], [2, 4]];
185
+ const TIC = [[0, 2], [1, 3], [2, 2], [3, 1], [4, 0]];
186
+ const CORAZON = [[1, 0], [3, 0], [0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [1, 2], [2, 2], [3, 2], [2, 3]];
187
+ const ZETA = [[0, 0], [1, 0], [2, 0], [1, 1], [0, 2], [1, 2], [2, 2]];
188
+ const NOTA = [[1, 0], [2, 0], [1, 1], [1, 2], [0, 3], [1, 3]];
189
+ export const ANIMACIONES = {
190
+ reposo: [{ ms: 1500 }, { ojos: 'cerrado', ms: 120 }, { ms: 1800 }, { ojos: 'derecha', ms: 700 }, { ms: 500 }, { izq: { dy: 1 }, der: { dy: 1 }, ms: 400 }, { ms: 900 }],
191
+ saludo: [
192
+ { ojos: 'feliz', ms: 200 },
193
+ { ojos: 'feliz', der: { dx: 1, dy: -1 }, ms: 180 },
194
+ { ojos: 'feliz', ms: 180 },
195
+ { ojos: 'feliz', der: { dx: 1, dy: -1 }, ms: 180 },
196
+ { ojos: 'feliz', ms: 600 },
197
+ ],
198
+ pensando: [
199
+ { ojos: 'arriba', ms: 350 },
200
+ { ojos: 'arriba', extra: P([[2, 1]]), ms: 350 },
201
+ { ojos: 'arriba', extra: P([[2, 1], [4, 1]]), ms: 350 },
202
+ { ojos: 'arriba', extra: P([[2, 1], [4, 1], [6, 0]]), ms: 600 },
203
+ { ojos: 'cerrado', extra: P([[2, 1], [4, 1], [6, 0]]), ms: 120 },
204
+ ],
205
+ trabajando: [
206
+ { izq: { dy: -1 }, extra: P([[1, 9], [15, 3]]), ms: 170 },
207
+ { der: { dy: -1 }, extra: P([[0, 7], [15, 6]]), ms: 170 },
208
+ ],
209
+ escribiendo: [
210
+ { ojos: 'abajo', extra: P([[3, 15]]), ms: 220 },
211
+ { ojos: 'abajo', extra: P([[3, 15], [5, 15]]), ms: 220 },
212
+ { ojos: 'abajo', extra: P([[3, 15], [5, 15], [7, 15]]), ms: 220 },
213
+ { ojos: 'abajo', ms: 220 },
214
+ ],
215
+ buscando: [{ ojos: 'izquierda', ms: 380 }, { ms: 200 }, { ojos: 'derecha', ms: 380 }, { ms: 200 }],
216
+ esperando: [{ ms: 450 }, { extra: P(INTERROGACION, 1, 1), ms: 650 }, { extra: P(INTERROGACION, 1, 1), ojos: 'cerrado', ms: 120 }, { extra: P(INTERROGACION, 1, 1), ms: 650 }],
217
+ hecho: [
218
+ { ms: 200 },
219
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', ms: 140 },
220
+ { izq: { dy: -2 }, der: { dy: -2 }, ojos: 'feliz', extra: P(TIC, 1, 1), ms: 240 },
221
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', extra: P(TIC, 1, 1), ms: 140 },
222
+ { ojos: 'feliz', extra: P(TIC, 1, 1), ms: 900 },
223
+ ],
224
+ error: [
225
+ { ojos: 'x', izq: { dx: -1 }, der: { dx: -1 }, ms: 90 },
226
+ { ojos: 'x', izq: { dx: 1 }, der: { dx: 1 }, ms: 90 },
227
+ { ojos: 'x', izq: { dx: -1 }, der: { dx: -1 }, ms: 90 },
228
+ { ojos: 'x', izq: { dx: 1 }, der: { dx: 1 }, ms: 90 },
229
+ { ojos: 'x', ms: 900 },
230
+ ],
231
+ dormido: [
232
+ { ojos: 'cerrado', extra: P(ZETA, 1, 6), ms: 600 },
233
+ { ojos: 'cerrado', izq: { dy: 1 }, der: { dy: 1 }, extra: P(ZETA, 1, 3), ms: 600 },
234
+ { ojos: 'cerrado', extra: P(ZETA, 2, 0), ms: 600 },
235
+ { ojos: 'cerrado', izq: { dy: 1 }, der: { dy: 1 }, ms: 600 },
236
+ ],
237
+ estirarse: [
238
+ { ms: 300 },
239
+ { izq: { estira: 1 }, der: { estira: 1 }, ojos: 'cerrado', ms: 220 },
240
+ { izq: { estira: 2 }, der: { estira: 2 }, ojos: 'cerrado', ms: 500 },
241
+ { izq: { estira: 1 }, der: { estira: 1 }, ojos: 'cerrado', ms: 200 },
242
+ { ojos: 'feliz', ms: 600 },
243
+ ],
244
+ salto: [
245
+ { izq: { dy: 1 }, der: { dy: 1 }, ms: 160 },
246
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', ms: 120 },
247
+ { izq: { dy: -2 }, der: { dy: -2 }, ojos: 'feliz', ms: 220 },
248
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', ms: 120 },
249
+ { izq: { dy: 1 }, der: { dy: 1 }, ms: 140 },
250
+ { ms: 500 },
251
+ ],
252
+ baile: [
253
+ { izq: { dy: -1, dx: -1 }, der: { dx: -1 }, ojos: 'feliz', extra: P(NOTA, 0, 1), ms: 220 },
254
+ { izq: { dx: 1 }, der: { dy: -1, dx: 1 }, ojos: 'feliz', ms: 220 },
255
+ { izq: { dy: -1, dx: -1 }, der: { dx: -1 }, ojos: 'feliz', ms: 220 },
256
+ { izq: { dx: 1 }, der: { dy: -1, dx: 1 }, ojos: 'feliz', extra: P(NOTA, 0, 0), ms: 220 },
257
+ ],
258
+ guino: [{ ms: 600 }, { ojos: ['abierto', 'cerrado'], ms: 260 }, { ojos: 'feliz', ms: 700 }],
259
+ corazon: [
260
+ { ojos: 'feliz', extra: P(CORAZON, 0, 3), ms: 260 },
261
+ { ojos: 'feliz', extra: P(CORAZON, 0, 2), ms: 260 },
262
+ { ojos: 'feliz', extra: P(CORAZON, 0, 1), ms: 260 },
263
+ { ojos: 'feliz', extra: P(CORAZON, 0, 0), ms: 700 },
264
+ ],
265
+ fiesta: [
266
+ { ojos: 'feliz', izq: { dy: -1 }, der: { dy: -1 }, extra: P([[1, 2], [4, 0], [15, 1], [14, 9], [0, 11]]), ms: 200 },
267
+ { ojos: 'feliz', extra: P([[2, 4], [5, 1], [14, 3], [15, 11], [1, 13]]), ms: 200 },
268
+ { ojos: 'feliz', izq: { dy: -1 }, der: { dy: -1 }, extra: P([[0, 6], [3, 0], [15, 5], [13, 13], [2, 15]]), ms: 200 },
269
+ { ojos: 'feliz', extra: P([[1, 8], [6, 0], [14, 7], [15, 14], [0, 1]]), ms: 200 },
270
+ ],
271
+ musica: [
272
+ { ojos: 'cerrado', izq: { dx: -1 }, der: { dx: -1 }, extra: P(NOTA, 1, 5), ms: 380 },
273
+ { ojos: 'cerrado', extra: P(NOTA, 1, 3), ms: 380 },
274
+ { ojos: 'cerrado', izq: { dx: 1 }, der: { dx: 1 }, extra: P(NOTA, 2, 1), ms: 380 },
275
+ { ojos: 'cerrado', extra: P(NOTA, 2, 0), ms: 380 },
276
+ ],
277
+ mirar: [{ ojos: 'izquierda', ms: 600 }, { ms: 250 }, { ojos: 'derecha', ms: 600 }, { ojos: 'arriba', ms: 500 }, { ms: 500 }],
278
+ sorpresa: [{ ms: 300 }, { ojos: 'grande', izq: { dy: -1 }, der: { dy: -1 }, extra: P(EXCLAMACION, 1, 1), ms: 200 }, { ojos: 'grande', extra: P(EXCLAMACION, 1, 1), ms: 900 }],
279
+ asomarse: [
280
+ { izq: { dy: 12 }, der: { dy: 12 }, ms: 500 },
281
+ { izq: { dy: 8 }, der: { dy: 8 }, ms: 140 },
282
+ { izq: { dy: 5 }, der: { dy: 5 }, ms: 140 },
283
+ { izq: { dy: 3 }, der: { dy: 3 }, ms: 900 },
284
+ { izq: { dy: 3 }, der: { dy: 3 }, ojos: 'cerrado', ms: 120 },
285
+ { izq: { dy: 3 }, der: { dy: 3 }, ojos: 'derecha', ms: 700 },
286
+ { izq: { dy: 6 }, der: { dy: 6 }, ms: 140 },
287
+ { izq: { dy: 12 }, der: { dy: 12 }, ms: 700 },
288
+ ],
289
+ };
290
+ export const LISTA_DE_ANIMACIONES = Object.keys(ANIMACIONES);
291
+ export const DEL_TRABAJO = ['saludo', 'pensando', 'trabajando', 'escribiendo', 'buscando', 'esperando', 'hecho', 'error'];
292
+ export function animacionDelEstado(estado) {
293
+ switch (estado) {
294
+ case 'trabajando':
295
+ return 'trabajando';
296
+ case 'espera':
297
+ return 'esperando';
298
+ case 'parado':
299
+ return 'error';
300
+ case 'hecho':
301
+ return 'hecho';
302
+ default:
303
+ return 'reposo';
304
+ }
305
+ }
306
+ const cache = new Map();
307
+ export function fotogramasDe(a, accesorio) {
308
+ const clave = `${a}|${accesorio ?? ''}`;
309
+ let f = cache.get(clave);
310
+ if (!f) {
311
+ f = ANIMACIONES[a].map((c) => ({ matriz: matrizDe(c, accesorio), ms: c.ms }));
312
+ cache.set(clave, f);
313
+ }
314
+ return f;
315
+ }
316
+ export function duracionDe(a) {
317
+ return fotogramasDe(a).reduce((s, f) => s + f.ms, 0);
318
+ }
319
+ export function fotogramaEn(a, t, accesorio) {
320
+ const fs = fotogramasDe(a, accesorio);
321
+ const total = duracionDe(a);
322
+ let r = ((t % total) + total) % total;
323
+ for (const f of fs) {
324
+ if (r < f.ms)
325
+ return f;
326
+ r -= f.ms;
327
+ }
328
+ return fs[fs.length - 1];
329
+ }
330
+ export function rectangulosDe(matriz, tono) {
331
+ const out = [];
332
+ matriz.forEach((fila, y) => {
333
+ let x = 0;
334
+ while (x < LADO) {
335
+ const c = fila[x];
336
+ const color = colorDe(c, tono);
337
+ if (!color) {
338
+ x++;
339
+ continue;
340
+ }
341
+ let fin = x + 1;
342
+ while (fin < LADO && fila[fin] === c)
343
+ fin++;
344
+ out.push({ x, y, ancho: fin - x, color });
345
+ x = fin;
346
+ }
347
+ });
348
+ return out;
349
+ }
350
+ export function svgDe(matriz, tono, px = 64) {
351
+ const r = rectangulosDe(matriz, tono)
352
+ .map((q) => `<rect x="${q.x}" y="${q.y}" width="${q.ancho}" height="1" fill="${q.color}"/>`)
353
+ .join('');
354
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" viewBox="0 0 16 16" shape-rendering="crispEdges">${r}</svg>`;
355
+ }