Skip to main content

adela-new-module

Scaffold y creación de nuevos módulos Adela siguiendo el ecosistema de piezas intercambiables

الانتقال إلى التثبيت

معلومات المصدر

المستودع
Ntizar/NtizarBrainMasterMind
آخر نشاط في المصدر
٢٦ يونيو ٢٠٢٦ في ١٢:٠٥
لغة SKILL.md المكتشفة
الإسبانية
النجوم
٢
التفرعات
٠

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
14 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
adela-new-module
description
Scaffold y creación de nuevos módulos Adela siguiendo el ecosistema de piezas intercambiables
## Adela New Module Skill para que un LLM cree un nuevo módulo Adela desde cero, siguiendo los estándares del ecosistema (TypeScript strict, zero-deps, TODO en castellano). ### Cuándo usarlo - El usuario dice "crea un módulo Adela para X" - Necesitas añadir una nueva pieza al ecosistema Adela - El usuario pide "otro módulo como los que ya tenemos" - El usuario da un roadmap multi-fase para mejorar Adela (seguridad, observabilidad, escalabilidad, API) — cargar `references/backend-roadmap.md` para entender las fases y categorías ### ⚠️ Regla CRÍTICA: Todos los repos Adela son PRIVADOS **Cada módulo Adela se crea como repositorio PRIVADO en GitHub.** El usuario lo exige explícitamente. NO crear repos públicos. ### Categorías de módulos (nuevas desde v2.0) | Categoría | Descripción | Ejemplos | |-----------|-------------|----------| | `infra` | Capa base — sin dependencias de otros Adela | time, env, http, cache, db | | `core` | Capa funcional — depende de infra | auth, health, ai | | `export` | Capa de exportación | export | | `presentation` | Capa de interfaz | i18n, admin | | `seguridad` | **NUEVA** — Seguridad y validación | security, rateLimit, validation | | `observabilidad` | **NUEVA** — Logging y errores | logger, errors | | `escalabilidad` | **NUEVA** — Escalado horizontal | db_pg, cache_redis | | `api-layer` | **NUEVA** — Capa de API | pagination, router | ### Principio de explicación El usuario quiere que **le expliques cómo funciona cada módulo**. Al crear un módulo nuevo: - README.md debe explicar la arquitectura y el flujo - Explicar por qué se eligió ese enfoque (no solo el qué, sino el porqué) - Incluir diagramas de flujo ASCII cuando sea relevante ### Pasos #### 1. Copiar template scaffold ```bash cp -r /root/workspace/AdelaMasterMind/templates/adela-module-scaffold/ /root/workspace/Adela/Adela_<NOMBRE>/ ``` #### 2. Rellenar package.json Reemplazar placeholders `{{MODULE_NAME}}`, `{{MODULE_NAME_LOWERCASE}}`, `{{MODULE_DESCRIPTION}}`. Reglas de dependencias: - **Zero runtime deps** siempre que sea posible - Si necesita base de datos → `sql.js` - Si necesita auth → `bcryptjs`, `jsonwebtoken` - Si necesita export → `csv-parse`, `csv-stringify`, `pdfkit` - Si necesita fetch → usar `fetch()` nativo de Node (18+) #### 3. Implementar src/ Estructura estándar: ``` src/ ├── index.ts # Barrel export: exporta todo ├── <modulo>.ts # Implementación principal con createX() factory └── types.ts # Interfaces públicas ``` **Patrón multi-archivo:** Cuando un módulo tiene responsabilidades separadas (ej: implementación + formato de salida), dividir en archivos: ``` src/ ├── index.ts # Barrel export ├── metrics.ts # Lógica principal (createMetrics, counter, histogram, gauge, middleware) ├── prometheus.ts # Formato de exportación (toPrometheusFormat, toPrometheusJSON) └── types.ts # Tipos públicos ``` Cada archivo debe ser autocontenido y testable por separado. `index.ts` solo re-exporta. **🔴 PITFALL — Token GitHub enmascarado:** El `GITHUB_TOKEN` en `/hermes-home/.env` se muestra como `***` (enmascaramiento visual) pero es un PAT real de 40 caracteres. Sin embargo, **puede expirar entre llamadas consecutivas**, causando "Bad credentials" intermitentes. **Patrón seguro:** siempre sourcear `.env` y usar el token en el mismo bloque de comando: ```bash source /hermes-home/.env 2>/dev/null # Usar $GITHUB_TOKEN inmediatamente en el mismo bloque curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" ... ``` Si el token falla, verificar si se expiró y reemplazarlo en `.env` con un nuevo PAT desde GitHub Settings → Developer Settings → Personal access tokens. **🔴 PITFALL CRÍTICO — JWT en ESM con jsonwebtoken:** `import * as jwt from 'jsonwebtoken'` crea un **namespace object** donde `jwt.verify` funciona pero `jwt.sign` NO existe como método directo. Esto causa fallos silenciosos: el login genera token OK pero el middleware lo rechaza como "inválido". **Solución:** usar `createRequire` en TODOS los archivos que usen jsonwebtoken (routes Y middleware): ```typescript import { createRequire } from 'node:module' const require = createRequire(import.meta.url) const jwt = require('jsonwebtoken') // Ahora jwt.sign Y jwt.verify funcionan ``` **NUNCA mezclar:** un archivo con `import * as jwt` y otro con `require('jsonwebtoken')` = tokens que no se verifican. **🔴 PITFALL CRÍTICO — JWT_SECRET compartido entre módulos:** Si cada archivo (routes/auth.ts, middleware/auth.ts) genera su propio `process.env.JWT_SECRET || crypto.randomUUID()`, cada uno crea un UUID diferente. Resultado: el token se firma con el UUID de auth.ts pero se verifica con el UUID de middleware/auth.ts → siempre falla como "Token inválido". **Solución:** un solo `config.ts` exporta el JWT_SECRET, todos los archivos importan de ahí: ```typescript // src/config.ts export const JWT_SECRET = process.env.JWT_SECRET || crypto.randomUUID() // routes/auth.ts import { JWT_SECRET } from '../config.js' // middleware/auth.ts import { JWT_SECRET } from '../config.js' ``` **NUNCA** generar JWT_SECRET inline en cada archivo. Siempre un módulo compartido. **🔴 PITFALL — better-sqlite3 vs sql.js:** `better-sqlite3` requiere compilación nativa (node-gyp + make). Si no hay `make` en el sistema, el `npm install` falla. En entornos sin herramientas de compilación, usar **`sql.js`** (JS puro, sin compilación): ```bash npm install sql.js ``` Importación: `import initSqlJs from 'sql.js'` (async init). **🔴 PITFALL — sql.js es en memoria: persistencia en archivo:** sql.js por defecto guarda todo en memoria. Si el contenedor se reinicia, se pierden TODOS los datos. Para persistencia, exportar a archivo tras cada operación de escritura: ```typescript import fs from 'fs' import path from 'path' const DB_PATH = process.env.DB_PATH || path.join(__dirname, '..', 'data', 'datos.db') // Al iniciar: cargar desde archivo si existe if (fs.existsSync(DB_PATH)) { const buffer = fs.readFileSync(DB_PATH) db = new SQL.Database(buffer) } else { db = new SQL.Database() } // Después de CADA db.run() (INSERT, UPDATE, DELETE): function saveDatabase() { const data = db.export() const buffer = Buffer.from(data) const dir = path.dirname(DB_PATH) if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) fs.writeFileSync(DB_PATH, buffer) } // Wrapper que guarda automáticamente: function run(sql: string, params: any[] = []) { db.run(sql, params) saveDatabase() // ← persistir tras cada escritura } ``` **Dockerfile:** crear directorio de datos y dar permisos: ```dockerfile RUN mkdir -p /app/data RUN chown -R appuser:appgroup /app ``` **Env var:** `DB_PATH=/app/data/datos.db` para rutas personalizadas. **🔴 PITFALL — sql.js INSERT con valores undefined:** Al hacer INSERT con sql.js, pasar `undefined` como valor de parámetro causa fallos silenciosos o errores de tipo. Siempre usar fallbacks: ```typescript // ❌ MAL — data.descripcion puede ser undefined db.run('INSERT INTO table (a, b, c) VALUES (?, ?, ?)', [id, data.nombre, data.descripcion]) // ✅ BIEN — fallbacks explícitos db.run('INSERT INTO table (a, b, c) VALUES (?, ?, ?)', [id, data.nombre, data.descripcion || '']) ``` Regla: `|| ''` para strings, `|| 0` para números, `|| null` para NULL explícito. **🔴 PITFALL — sql.js INSERT column ordering:** Verificar que las columnas del INSERT coinciden con los valores. Un error común es poner `rol` como entero `1` en vez de string `'admin'` en el INSERT del usuario admin. **🔴 PITFALL — sql.js db.run() devuelve void/Promise, no el objeto creado:** `db.run()` NO devuelve el registro insertado. Si una función como `crearLead()` llama `db.run()` y luego construye el objeto manualmente, la función DEBE ser `async` y hacer `await db.run(...)`. Si la función del route no hace `await`, Express serializa la Promise como `{}` → respuesta vacía. Síntoma: `{"lead":{}}` en POST, `{"leads":{}}` en GET, `{"stats":{}}` en stats. El objeto se construye bien internamente pero Express lo serializa vacío porque la función devuelve una Promise sin await. **✅ Fix:** TODAS las funciones de route que llaman a `db.*()` deben ser `async` + `await`. TODAS las funciones en db.ts que llaman `db.run()` o `db.exec()` deben ser `async` + `await`. **🔴 PITFALL — Template literal SQL: backtick de cierre al añadir tablas con patch:** Al añadir un nuevo bloque `db.run(\`...\`)` al final de un bloque SQL existente usando `patch()`, el `old_string` DEBE incluir el backtick de cierre `\`)` del bloque anterior. Si no, se deja un `db.run()` sin cerrar, causando TS1005 '`,` expected' en todas las líneas SQL siguientes. ```typescript // ❌ MAL — old_string sin el backtick de cierre del bloque anterior // old_string: "CREATE INDEX ... ;\n\n // Admin user" // Resultado: el db.run() de las tablas base se queda abierto → TS1005 en todas las tablas nuevas // ✅ BIEN — old_string incluye el cierre del bloque anterior // old_string: "CREATE INDEX ... ;\n `)\n\n // Admin user" // Resultado: el bloque anterior se cierra correctamente, el nuevo abre su propio db.run() // new_string correcto: // "CREATE INDEX ... ;\n `)\n\n // === Nuevo módulo ===\n db.run(`\n ...\n `)\n\n // Admin user" ``` **🔴 PITFALL — createRequire no va en types.ts:** `createRequire` es para archivos runtime (db.ts, routes, middleware). Si se pone en `types.ts`, causa error TS1343: `import.meta` meta-property only allowed with module es2020/esnext/node16+. types.ts solo debe tener interfaces y tipos, nunca imports de runtime. **🔴 PITFALL — Migración de datos al cambiar schema (admin pin_hash NULL):** Cuando se actualiza el código para usar bcrypt en PINs, los usuarios existentes tienen `pin_hash = NULL` (creados con el código viejo). El login falla silenciosamente porque `bcrypt.compareSync(pin, null)` siempre devuelve false. **Solución — migración automática en initDatabase():** ```typescript // Después de CREATE TABLE, detectar y migrar: const adminRow = db.exec("SELECT id, pin_hash FROM usuarios WHERE email = 'admin@adelacrm.local'") if (adminRow[0]?.values[0][1] === null) { const pinHash = bcrypt.hashSync(String(process.env.ADMIN_PIN || '1234'), 10) db.run("UPDATE usuarios SET pin_hash = ? WHERE email = 'admin@adelacrm.local'", [pinHash]) saveDatabase() } ``` **Regla:** Siempre que se cambie el formato de un campo (plain → hash, string → enum, etc.), añadir migración automática en initDatabase() que detecte el formato viejo y lo actualice. Nunca asumir que la BD está vacía. **🔴 PITFALL — API devuelve campos sensibles (pin_hash, password):** Nunca devolver `pin_hash`, `password`, ni otros campos sensibles en respuestas API. Crear un helper `sanitizeUser()` en la ruta: ```typescript function sanitizeUser(u: any) { const { pin_hash, ...rest } = u return rest } // En GET /usuarios: const usuarios = await obtenerUsuarios() res.json({ usuarios: usuarios.map(sanitizeUser) }) // En POST /usuarios (respuesta): res.status(201).json({ usuario: sanitizeUser(usuario) }) ``` **🔴 PITFALL — Admin PIN hardcodeado:** Nunca hardcodear PINs de admin en el código fuente. Usar variable de entorno: ```typescript // En db.ts, al crear el admin inicial: const adminPin = process.env.ADMIN_PIN || '1234' const pinHash = bcrypt.hashSync(String(adminPin), 10) db.run("INSERT INTO usuarios ... VALUES (?, ?, ?, ?, ?, ?, ?)", ['admin-001', 'Administrador', 'admin@adelacrm.local', pinHash, 'admin', 1, ahora]) ``` Documentar en `.env.example`: ``` ADMIN_PIN=1234 JWT_SECRET=tu-secreto-super-seguro DB_PATH=/app/data/datos.db ``` **🔴 PITFALL — PIN visible en HTML (login hint):** Nunca mostrar el PIN real en el HTML del login. En vez de `Demo: admin@local / 1234`, usar: ```html <p class="hint">Credenciales de demostración en el README</p> ``` **🔴 PITFALL — Healthcheck en Dockerfile apunta a endpoint inexistente:** Verificar que el healthcheck del Dockerfile usa el endpoint REAL del servidor. Error común: Dockerfile dice `/healthz` pero el servidor expone `/health`. ```dockerfile # ❌ MAL — endpoint no existe CMD wget -qO- http://localhost:9000/healthz || exit 1 # ✅ BIEN — coincide con el servidor CMD wget -qO- http://localhost:9000/health || exit 1 ``` **🔴 PITFALL — Express req.params.id es string | string[], no string:** Al leer parámetros de ruta en Express, `req.params.id` devuelve `string | string[]` (el tipo de Express), no `string` directamente. Si se pasa a una función que espera `string`, TypeScript lanza TS2345. ```typescript // ❌ MAL const item = await obtenerPorId(req.params.id) // TS2345: string | string[] no asignable a string // ✅ BIEN — castear siempre const id = req.params.id as string const item = await obtenerPorId(id) ``` Regla: SIEMPRE hacer `const id = req.params.id as string` en handlers de rutas Express, tanto en GET/PUT/DELETE de una entidad como en rutas anidadas (`req.params.presupuestoId`, `req.params.lineaId`, etc.). Las rutas estáticas (ej: `/stats`) deben definirse ANTES de las rutas con parámetros (ej: `/:id`). Si no, Express captura `/stats` como un ID. Esto es la causa #1 de "endpoint no encontrado" en Express. **🔴 PITFALL — TypeScript module resolution:** La combinación que funciona para proyectos ESM con `import.meta`, `createRequire`, y default imports depende del tipo de módulo: **Para módulos backend (Express, sql.js, JWT):**
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub