--- type: DC asset_id: DC-EL-IntelliBanks-DescripcionProducto-v01 version: v01 tipo: DC — Documento de contexto (descripción de producto) status: 🟢 vigente · refleja código y operaciones a julio 2026 owner: Alex sherpa: SherpaX Alex ratificador: Victor Heredia intellibank: IB-EL-EmpowerLabs subbank: EQ-EL-Equipo fecha_creacion: 2026-07-07 proposito: > Descripción enriquecida del producto IntelliBanks: qué es, arquitectura, motor de sync, features, modelo de datos, restricciones. Fuente de contexto para el diseño ARQ Multi-Empresa + Zero-Trust (TP-EL-IntelliBanks- MultiEmpresa-ZeroTrust-FableBrief-v01, Sección 2). relacionado: - TP-EL-IntelliBanks-MultiEmpresa-ZeroTrust-FableBrief-v01 - SP-EL-IntelliBanks-PlanAccionFable5-20260707-v01 tags: [DC, intellibanks, producto, arquitectura, sync, contexto] --- # Intellibanks — Descripción de Producto (Enriquecida) > Documento vivo. Integra las especificaciones base del proyecto con los avances de la sesión de Julio 2026. Para cada capacidad se explica **qué hace** y, en modo semi-técnico, **cómo funciona por dentro**. --- ## 1. Qué es Intellibanks Intellibanks es una aplicación de **escritorio y web** para gestionar bancos de conocimiento basados en archivos. Nació para administrar "skills" (prompts y documentos operativos de IA), pero su arquitectura es genérica: maneja cualquier contenido editorial estructurado como Markdown, más binarios asociados (imágenes, PDF, DOCX, XLSX, HTML). Su diferenciador no es el editor, sino el **motor de sincronización bidireccional** que mantiene una carpeta local del usuario (`~/Documents/Intellibanks/`) espejada contra un backend central, en tiempo real y sin fricción. El usuario trabaja con sus archivos como archivos normales en su disco (Obsidian, Finder, VS Code, lo que sea) y la nube se mantiene al día sola. **Cómo está construido (resumen):** - **Frontend:** Angular 15 + TypeScript (strict) + RxJS. Estado global con `BehaviorSubjects` (sin NgRx). - **Escritorio:** Cordova + Electron (compila para macOS y Windows desde la misma base JS/TS). - **Daemon de sync:** proceso Node (`intellibanks-sync.js`) lanzado por Electron vía `utilityProcess.fork`, con `chokidar` vigilando el filesystem. - **Backend:** PHP **5.6** sobre `https://genniux.net/skills-api/api/`, con MySQL (PDO) y un repositorio Git como almacenamiento versionado de archivos. La versión 5.6 impone una restricción dura: nada de sintaxis PHP 7+ (`??`, arrow functions, etc.). --- ## 2. La experiencia de trabajo **Layout de tres paneles** (`HomeComponent`): a la izquierda el árbol de carpetas (`ExplorerComponent`), al centro la lista de archivos (`SkillListComponent`), a la derecha el visor con metadatos (`SkillPreviewComponent`). Es responsivo: en pantallas medianas colapsa a dos columnas y en móvil a una con pestañas. - **Sidebar colapsable a 0px.** Un clic en el logo (o el botón de expandir del visor) oculta el panel lateral y lleva el previewer al 100% del ancho. Útil para lectura/edición inmersiva. - **Pestañas tipo Obsidian.** El visor permite tener varios archivos abiertos a la vez. Cómo funciona: `SkillService` mantiene un estado `openTabs$`; al seleccionar un archivo se abre o activa su pestaña; cada pestaña muestra ícono por tipo, nombre y botón de cierre; al cerrar una, `closeTab()` activa la vecina. --- ## 3. Gestión de contenido **Editor Markdown (`EditorComponent`).** Toolbar de formato (negrita, itálica, encabezados, listas, tablas, links con sus atajos), preview en vivo y contador de palabras/caracteres. Cómo funciona el preview: `marked` renderiza el Markdown a HTML y `DOMPurify` lo sanea antes de pintarlo. El panel de preview además es **contentEditable**: los cambios que el usuario hace directamente sobre el HTML se convierten de vuelta a Markdown con `turndown`. Se guarda con `Ctrl+S` contra `update_skill.php`. **Importación de DOCX.** El usuario arrastra un `.docx`; se procesa del lado del cliente extrayendo su HTML, `DOMPurify` lo sanea, `turndown` lo pasa a Markdown y queda listo en el editor para revisión. Al guardar se persiste como `.md`. (La exportación inversa MD→DOCX no está implementada; `turndown` es unidireccional.) **Renderizado seguro de HTML.** Los archivos `.html` se muestran en un `iframe` con `srcdoc` alimentado por el contenido del archivo, lo que garantiza aislamiento visual manteniendo el mismo origen. El detalle fino: la app intercepta los clics dentro del iframe en **fase de captura** (`useCapture: true`) sobre el `contentDocument`, para frenar cualquier navegación relativa (`href` o `data-path`) antes de que el navegador intente cambiar de frame — eso evitaba los `ERR_FILE_NOT_FOUND` contra directorios temporales de Cordova. Cuando el clic apunta a otro documento local, en modo Electron se lee el archivo de disco, se busca su ID en el mapa de sync y se construye un `Skill` virtual que se renderiza al instante dentro del propio visor. --- ## 4. Motor de sincronización (el corazón del producto) El daemon (`intellibanks-sync.js`) vigila `~/Documents/Intellibanks/` y mantiene la paridad local↔nube. Piezas clave y cómo funcionan: **Arranque bidireccional.** Al abrir la app corre un ciclo completo (`runSyncCycle`). Si es el primer arranque (el mapa `.intellibanks_sync_map.json` está vacío) descarga en masa todo el servidor. En arranques posteriores compara nube contra disco: sube primero lo local nuevo o más reciente y baja las actualizaciones remotas. **Sincronización basada en mapa remoto (rediseño de rendimiento).** El diseño viejo hacía un crawl recursivo (una petición `list_skills.php` por carpeta), que en un vault de ~2,500+ skills tardaba **más que los 120 s** del ciclo, y ese solapamiento era la causa raíz de las malas sincronizaciones. Se reemplazó por **`get_sync_map.php`**, que arma desde la base de datos, en **una sola petición**, el mapa completo `{ skill_id, name, folder_path[], version, hash, content_ext }`. El hash **espeja** el cálculo del cliente (hash de los bytes crudos en disco) para poder comparar de forma fiable. Nombres y rutas se devuelven crudos: el cliente aplica su propia `cleanFileName`, que es la **única fuente de verdad** para el nombrado (así no divergen las reglas JS y PHP). El contenido de cada archivo se baja **on-demand** con `get_skill_content.php` (en base64 si es binario, para no romper `json_encode`), solo de los que cambiaron. **Caché de mapa en servidor (`true_map.json`, TTL 4 min).** Reconstruir el mapa desde la BD en cada consulta era caro. Ahora `get_sync_map.php` cachea el resultado en disco y lo reusa si tiene menos de 4 minutos; si el directorio del API no es escribible por `www-data`, cae automáticamente a `sys_get_temp_dir()`. Devuelve HIT/MISS según `filemtime`. **Arranque no bloqueante / local-first.** Antes la app quedaba "trabada" mientras `list_skills.php` cargaba. Ahora, al entrar, pinta el sidebar con lo que ya hay en el mapa local y deja que la actualización contra el servidor corra en segundo plano (**stale-while-revalidate**): se muestra lo cacheado al instante y se refresca cuando llega la respuesta nueva. **Fix de RAM (Fase 1: metadatos sin contenido).** `list_skills.php` devolvía el contenido completo de cada archivo, lo que disparaba el consumo de memoria. Se rediseñó para devolver **solo metadatos** (`systemPrompt` vacío) y cargar el contenido bajo demanda cuando el usuario abre un archivo (leyéndolo del disco local vía el mapa, o del servidor como fallback). `search.php` quedó igual: busca por nombre/descripción, no por contenido. **Pausa de sincronización durante subidas.** Para no competir con una carga activa, el ciclo periódico se **pausa mientras se están subiendo archivos** y se reanuda tras **3 minutos sin subidas**. Cómo funciona: cada subida sella `lastUploadAt = Date.now()`; el `setInterval` de 120 s revisa `if (lastUploadAt > 0 && (now - lastUploadAt) < 3 min) return;`. **Omitir dotfiles y dotfolders.** El sincronizador **nunca** sube rutas ocultas (cualquier segmento que empiece con `.`: `.obsidian`, `.claude`, `.git`, `.intellibanks_sync_map.json`, etc.). En esta sesión se endureció esta regla en **cuatro capas**: (1) un helper `pathHasDotSegment()` que revisa *todos* los segmentos de la ruta relativa —no solo el nombre del archivo, así cae también `.obsidian/config.json`—; (2) el filtro `ignored` de chokidar usa ese helper sobre la ruta completa; (3) una guarda temprana en el manejador de eventos; y (4) guardas finales en `uploadNewFile`/`updateExistingFile` que abortan con log `DOTFILE-SKIP`. La reconciliación periódica ya filtraba por nivel, así que tampoco crea carpetas ocultas en la nube. **Guardas anti-pérdida (defensa en profundidad).** El cliente nunca escribe un archivo de texto vacío, nunca sobrescribe uno bueno con contenido vacío, y nunca **sube** contenido vacío sobre un skill existente (protege la nube del equipo). En el servidor, `update_skill.php` y `upload.php` rechazan subidas vacías/de 0 bytes validando `UPLOAD_ERR_OK` + contenido tras `trim()` — es la capa definitiva ante cualquier cliente. (Esto resolvió el bug crítico donde el campo `content` vs `systemPrompt` provocaba archivos de 0 bytes que machacaban los locales.) **Procesamiento secuencial y resiliencia.** Todos los eventos (en vivo por chokidar o del ciclo) se encolan y procesan de a uno, eliminando condiciones de carrera. El cliente valida el `content-type` de cada respuesta: si no es JSON (un 500 de Apache, un warning, etc.) lo vuelca a consola en vez de reventar con `Unexpected end of JSON input`. Y si `get_sync_map.php` falla, **omite el ciclo** completo antes que sincronizar con datos incompletos. --- ## 5. Búsqueda Campo con **debounce de 300 ms** (`debounceTime`), `distinctUntilChanged()` para no repetir consultas y `switchMap()` para cancelar peticiones en vuelo al seguir tecleando. Los resultados de `search.php` reemplazan la lista actual, y al clickear uno el servicio determina jerárquicamente la carpeta padre, navega a `/c/:id_carpeta` (o `/home`) y selecciona el archivo. **Fix de cuelgue con palabras largas.** La búsqueda se congelaba con términos de 6–7+ caracteres: `search.php` metía el contenido binario crudo en `systemPrompt`, `json_encode` fallaba y devolvía vacío. Se corrigió a búsqueda solo por nombre/descripción, y del lado del cliente se agregó un `timeout` de RxJS a los pipes de búsqueda para que **nunca** quede colgada aunque el servidor tarde. --- ## 6. Organización: carpetas y autoría obligatoria Carpetas con **anidamiento ilimitado** (árbol recursivo), breadcrumb dinámico, y CRUD vía `folders.php` (`action = create | update | delete`). Cada carpeta lleva contadores (`fileCount`, `skillsCount`, `foldersCount`, `sharedCount`). **Autoría obligatoria (nuevo).** Se detectó que existían carpetas con `created_by = NULL` (p. ej. "Templates"). La causa: se creaban desde la web **sin sesión iniciada** —el frontend solo adjuntaba `usuario` si había login— y `folders.php` permitía insertar el autor en NULL. Se cerraron las dos fugas: (1) `folders.php` ahora **rechaza** la creación si no hay autor resoluble (HTTP 400, nunca inserta NULL); (2) el `createFolder()` del frontend bloquea la acción con aviso "Debes iniciar sesión…" antes de llamar al API. El daemon nunca cayó en esto porque siempre manda al menos `desktop_daemon`. --- ## 7. Compartir y deep-linking **Compartir por URL** a nivel de archivo individual y de **carpeta completa**, con modal dinámico para autorizar/revocar correos en tiempo real. Las carpetas restringidas se bloquean visualmente en la UI para usuarios ajenos (los permisos se gestionan hoy de forma sintética local por restricciones de FK en el backend). **Esquema `intellibanks://` (nuevo).** Se implementó un protocolo propio para abrir archivos y carpetas directamente en la app desde enlaces externos (por ejemplo, desde un dashboard o un documento). Cómo funciona: en el empaquetado de Electron se registra el esquema (`setAsDefaultProtocolClient`, con manejo dev-aware por `process.defaultApp`; en macOS además se parcha el `Info.plist` con `CFBundleURLTypes` vía un hook `after_build`). El proceso principal captura la URL (evento `open-url`, `second-instance` con `requestSingleInstanceLock`, y el `argv` en arranque en frío), la reenvía al renderer (`webContents.send`) y `AppComponent` la procesa dentro de `NgZone`: `intellibanks://open?id=…` abre un skill, `?folder=…` navega a la carpeta y `?path=…` abre un archivo local por ruta. El **modal de compartir** ahora genera estas URLs, de modo que un clic en el enlace abre Intellibanks y lleva directo al recurso. --- ## 8. Panel de administración Vista exclusiva del usuario `JoseRojas` (`admin.component.ts` + `admin_api.php`). Historia relevante: el admin salía en blanco porque `admin_api.php` usaba `??` (PHP 7) que no ejecuta en 5.6 — se reemplazó por ternarios `isset()`, y todas las fechas se devuelven en `America/Mexico_City` (`ib_to_mexico()`). **Tarjetas de estadísticas y conteos locales.** Muestran totales del servidor (archivos, carpetas, vacías, posibles duplicados, sincronizaciones) y, en pequeño, cuántas carpetas/archivos hay **localmente** para comparar. Cómo se calcula el conteo local: se recorre el filesystem con las funciones de Node expuestas por `contextBridge`, detectando directorios por **bits de modo** (`(mode & 0o170000) === 0o040000`) porque los objetos `Stats` pierden sus métodos (como `isDirectory()`) al cruzar el puente de Electron. **Comparación real por ruta (no resta cruda).** La tarjeta "Total de Archivos" antes restaba totales (remoto − local), lo que engañaba porque el conteo remoto incluye duplicados/huérfanos de la BD. Ahora la tarjeta usa la **misma comparación por ruta** que el modal: al cargar el admin dispara una diferencia real y muestra "sincronizado", "N por subir" o "N faltan en local" — sin números negativos. **Modal de diferencias local vs remoto.** Al hacer clic en la tarjeta de archivos se abre un modal con dos listas con scroll: **"Faltan en LOCAL"** (están en el servidor pero no en disco) y **"Faltan en REMOTO"** (están en local pero no subidos). Cómo funciona: trae el mapa remoto (`get_sync_map.php`), recorre el filesystem local, construye las claves de ruta con la misma lógica de nombrado del motor de sync y agrupa por ruta en un `Map`. Un botón **"Descargar faltantes"** baja secuencialmente cada archivo ausente vía `get_skill_content.php`, lo escribe creando su carpeta, muestra progreso y al terminar recalcula la diferencia. **Otras vistas.** Estructura de directorios, actividad reciente, posibles duplicados (`get_duplicates`), historial de sincronizaciones (`get_sync_history`, últimas 100, excluye corridas 0-0) con **tooltips flotantes** que al pasar el mouse por ↑/↓/⚠ listan qué archivos se movieron o qué falló, y detalle de archivo (`get_file_detail`) con metadatos, historial de versiones y acción de **eliminar** (usa `delete_skill.php`, borrado completo BD + storage + Git). --- ## 9. Integridad y saneamiento de datos **Herramienta `admin_cleanup.php` (nuevo).** Endpoint de mantenimiento con salvaguardas, compatible con PHP 5.6. Acciones: - **`analyze`** (solo lectura): clasifica las filas de `skills` — huérfanas sin versión actual, con archivo faltante en disco, con carpeta inexistente, y **duplicados por (carpeta + nombre)** con muestras. También reporta carpetas sin autor. No modifica nada. - **`backup`**: exporta `skills`, `skill_versions` y `skill_folders` a un `.sql` con INSERTs (con `pdo->quote`, `group_concat_max_len` elevado para no truncar ids). - **`cleanup`**: por defecto **dry-run**; para borrar exige `confirm=SI`, `dry_run=0` y **respaldo previo existente**. Política conservadora: elimina huérfanas y los sobrantes de cada grupo de duplicados, conservando la fila de mayor `current_version` y **siempre** dejando una por grupo. Todo va en una transacción con rollback. - **`fix_folder_authors`**: rellena `created_by` NULL/'' a `sistema` (dry-run + backup obligatorio). **Limpieza ejecutada en producción (Julio 2026).** El diagnóstico reveló que de ~4,942 filas en `skills`, **2,154 eran duplicados** por ruta (p. ej. `team-settings.json` y `rally_progress.json` con 905 copias cada uno — una carpeta física solo puede tener un archivo con ese nombre, así que eran registros basura). Se respaldó (12,935 filas), se ejecutó la limpieza y la tabla quedó en **2,788** filas reales. Además se corrigieron **18 carpetas** con autor NULL. Nota operativa: las lecturas GET a la API se cachean por CDN, lo que dio un falso "no persistió"; se verificó con un parámetro anti-caché que ambas operaciones sí aplicaron. > **Causa raíz pendiente:** los duplicados crecen solos (subieron +40 en minutos), señal de que `upload.php` crea un skill nuevo en vez de actualizar el existente para ciertos archivos que cambian seguido (los `.json`). Corregir ese dedup es el siguiente pendiente para que la limpieza no se revierta. --- ## 10. Auditoría, versiones y robustez del backend **Auditoría por usuario.** Las tablas `skills`/`skill_versions` llevan `created_by`/`updated_by`. Cada creación o actualización deja un registro en `skill_activity_logs` con usuario, tipo de acción, entidad, nombre de archivo, detalles, IP y timestamp. **Historial de sincronizaciones (`user_sync_history`).** Cada corrida registra usuario, origen (`DESKTOP`/`WEB`), inicio con estado `IN_PROGRESS` y, al terminar, `SUCCESS`/`FAILED` con conteos de subidos/bajados/errores y una bitácora JSON. Se alimenta desde `log_sync.php` (`start`/`end`). **Versionado Git.** Cada archivo tiene historial versionado (respaldado por Git en el backend); el usuario ve todas las versiones con fecha y descripción, y restaura con un clic (`restaurar.php`). **Fix de `HTTP_ORIGIN` (estabilidad del servidor).** El servidor se caía durante la sincronización: el motor Node no manda el header `Origin` (solo lo mandan los navegadores), y `db.php` lo leía sin guarda, disparando un `Notice: Undefined index: HTTP_ORIGIN` que se inyectaba en el JSON, rompía el sync y provocaba reintentos que saturaban el servidor. Se corrigió con `isset()` y una lista blanca de orígenes. `HTTP_ACCESS_CONTROL_REQUEST_HEADERS` quedó igualmente protegido. **Candado de subidas (modo desarrollo · solo `JoseRojas`).** Un switch en el header (visible solo para ese usuario) bloquea/desbloquea las subidas a producción mientras se desarrolla, vía la bandera `.intellibanks_upload_lock.json` que `isUploadLocked()` lee antes de cada subida. Para el resto del equipo no aplica. --- ## 11. Modelo de datos (resumen) - **`skills`** — archivo de contenido: `id`, `name`, `description`, `status`, `current_version`, `folder_id`, `created_by`, `updated_by`, timestamps. El `systemPrompt` (contenido Markdown) vive en el almacenamiento versionado, no en la lista. - **`skill_versions`** — snapshots por versión con `is_current`, `file_path`, `created_by`. - **`skill_folders`** — árbol de carpetas: `id`, `name`, `parent_id`, `created_by`, timestamps. - **`skill_activity_logs`** y **`user_sync_history`** — auditoría y bitácora de sync descritas arriba. --- ## 12. En el radar (documentado, no liberado) - **Multi-empresa / multi-tenant.** Análisis para segmentar contenido por compañía (`origen` ≈ `company_id`). Documentado en `ANALISIS-MultiEmpresa.md`. - **Fase 2 · local-first.** Profundizar el modelo donde el disco local es la fuente primaria y la nube el respaldo/colaboración. Documentado en `ANALISIS-Fase2-LocalFirst.md`. - **Integración de auth con `genniux_board`.** Login/registro/recuperación unificados para gestionar accesos y restricciones. Documentado en `PROPUESTA-Integracion-Auth.md`. - **Intellibanks para Linux/Ubuntu.** Viabilidad analizada (la base Electron lo permite; falta empaquetado y registro de esquema para Linux). - **Corrección de la causa raíz de duplicados** en `upload.php` (dedup por ruta para archivos que cambian seguido). --- ## 13. Restricciones y convenciones del proyecto - **Rama de trabajo:** siempre la rama activa del desarrollador (normalmente `alex`); no usar ramas ocultas. - **Compatibilidad:** todo el cliente es JS/TS puro que compila para macOS y Windows. - **Backend en producción:** PHP **5.6**; prohibido `??`, arrow functions y demás sintaxis de PHP 7+. Antes de tocar el backend, **siempre** se respalda el archivo original. - **Nombrado canónico:** `cleanFileName` en el cliente es la única fuente de verdad para construir nombres/rutas. --- *Documento de producto generado a partir de `ESPECIFICACIONES.md` y de los avances de la sesión de Julio 2026. Las secciones de arquitectura reflejan el código fuente; las de saneamiento reflejan operaciones ya ejecutadas en producción con respaldo.*