wadi 1.0.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.
Files changed (294) hide show
  1. package/.agent/workflows/auto_sync.md +19 -0
  2. package/.agent/workflows/kivo_pipeline.md +27 -0
  3. package/.devcontainer/devcontainer.json +27 -0
  4. package/.github/workflows/kivo-cicd.yml +45 -0
  5. package/.github/workflows/monorepo-ci.yml +51 -0
  6. package/.github/workflows/wadi-ci.yml +38 -0
  7. package/.husky/pre-commit +2 -0
  8. package/.prettierignore +12 -0
  9. package/.prettierrc +9 -0
  10. package/.vscode/settings.json +19 -0
  11. package/CHANGELOG.md +56 -0
  12. package/CODE_OF_CONDUCT.md +43 -0
  13. package/CONTRIBUTING.md +42 -0
  14. package/DEPLOY_GUIDE.md +143 -0
  15. package/GO_LIVE_CHECKLIST.md +41 -0
  16. package/GO_LIVE_COMPLETE_REPORT.md +58 -0
  17. package/GO_LIVE_VALIDATION.md +39 -0
  18. package/LEGAL.md +38 -0
  19. package/MANIFESTO.md +40 -0
  20. package/MANUAL.md +82 -0
  21. package/OPS_PLAN.md +90 -0
  22. package/README.md +126 -0
  23. package/RELEASE_NOTES.md +51 -0
  24. package/ROADMAP.md +51 -0
  25. package/api_listado.txt +2197 -0
  26. package/apps/api/WADI_PROTOCOL.md +52 -0
  27. package/apps/api/debug-brain.js +11 -0
  28. package/apps/api/package.json +21 -0
  29. package/apps/api/src/core/analisis.js +17 -0
  30. package/apps/api/src/core/errors.js +31 -0
  31. package/apps/api/src/core/logger.js +32 -0
  32. package/apps/api/src/core/prompt-kivo.js +62 -0
  33. package/apps/api/src/index.js +212 -0
  34. package/apps/api/src/layers/human_pattern/composeResponse.js +35 -0
  35. package/apps/api/src/layers/human_pattern/detectPattern.js +39 -0
  36. package/apps/api/src/layers/human_pattern/heuristics.js +28 -0
  37. package/apps/api/src/layers/human_pattern/index.js +28 -0
  38. package/apps/api/src/layers/human_pattern/socialMemory.js +35 -0
  39. package/apps/api/src/middleware/errorHandler.js +24 -0
  40. package/apps/api/src/middleware/rateLimiter.js +38 -0
  41. package/apps/api/src/middleware/requestLogger.js +31 -0
  42. package/apps/api/src/middleware/upload.js +21 -0
  43. package/apps/api/src/middleware/validation.js +70 -0
  44. package/apps/api/src/modules/data.js +25 -0
  45. package/apps/api/src/modules/marketing.js +54 -0
  46. package/apps/api/src/modules/projects.js +40 -0
  47. package/apps/api/src/openai.js +16 -0
  48. package/apps/api/src/preferences/index.js +20 -0
  49. package/apps/api/src/register_user.js +22 -0
  50. package/apps/api/src/routes/kivo.js +58 -0
  51. package/apps/api/src/routes/monitoring.js +55 -0
  52. package/apps/api/src/routes.js +656 -0
  53. package/apps/api/src/supabase.js +17 -0
  54. package/apps/api/src/tools/index.js +57 -0
  55. package/apps/api/src/wadi-brain.js +171 -0
  56. package/apps/api/supabase/migrations/20251218_audit_logs.sql +27 -0
  57. package/apps/api/supabase/migrations/v2-chat-persistence.sql +83 -0
  58. package/apps/api/supabase/migrations/v3-cascade-delete.sql +11 -0
  59. package/apps/api/supabase/migrations/v3-security-fix.sql +90 -0
  60. package/apps/api/supabase/migrations/v3-storage.sql +36 -0
  61. package/apps/api/supabase/migrations/v4-gamification.sql +83 -0
  62. package/apps/api/supabase/migrations/v5-smoke-index.sql +6 -0
  63. package/apps/api/supabase/migrations/v6-schema-integrity-fix.sql +98 -0
  64. package/apps/api/supabase/migrations/v7-performance-indexes.sql +5 -0
  65. package/apps/api/supabase/migrations/v7-security-hardening.sql +132 -0
  66. package/apps/api/supabase/migrations/v8-security-hardening.sql +79 -0
  67. package/apps/api/test_human_pattern.js +30 -0
  68. package/apps/api/test_human_pattern_vague.js +30 -0
  69. package/apps/api/test_output.txt +76 -0
  70. package/apps/api/test_vague.js +30 -0
  71. package/apps/api/test_vague_block.js +30 -0
  72. package/apps/api/test_wadi.js +31 -0
  73. package/apps/api/tsconfig.json +13 -0
  74. package/apps/frontend/.env.local +3 -0
  75. package/apps/frontend/README.md +73 -0
  76. package/apps/frontend/eslint.config.js +27 -0
  77. package/apps/frontend/index.html +49 -0
  78. package/apps/frontend/package.json +41 -0
  79. package/apps/frontend/postcss.config.cjs +6 -0
  80. package/apps/frontend/public/cursors/wadi-neutral.svg +1 -0
  81. package/apps/frontend/public/cursors/wadi-select.svg +1 -0
  82. package/apps/frontend/public/icon-192.svg +1 -0
  83. package/apps/frontend/public/icon-512.svg +1 -0
  84. package/apps/frontend/public/manifest.webmanifest +23 -0
  85. package/apps/frontend/public/sw.js +57 -0
  86. package/apps/frontend/public/vite.svg +1 -0
  87. package/apps/frontend/public/wadi.svg +5 -0
  88. package/apps/frontend/src/assets/react.svg +1 -0
  89. package/apps/frontend/src/components/AuthLoader.tsx +50 -0
  90. package/apps/frontend/src/components/ChatInput.tsx +272 -0
  91. package/apps/frontend/src/components/ChatInterface.tsx +202 -0
  92. package/apps/frontend/src/components/ErrorBoundary.tsx +52 -0
  93. package/apps/frontend/src/components/InputArea.tsx +201 -0
  94. package/apps/frontend/src/components/Layout.tsx +73 -0
  95. package/apps/frontend/src/components/MessageBubble.tsx +66 -0
  96. package/apps/frontend/src/components/OnboardingModal.tsx +108 -0
  97. package/apps/frontend/src/components/SettingsModal.tsx +187 -0
  98. package/apps/frontend/src/components/Sidebar.tsx +171 -0
  99. package/apps/frontend/src/components/WadiOnboarding.tsx +71 -0
  100. package/apps/frontend/src/components/auditor/AuditReport.tsx +166 -0
  101. package/apps/frontend/src/components/auditor/AuditorHeader.tsx +34 -0
  102. package/apps/frontend/src/components/auditor/ContextPanel.tsx +138 -0
  103. package/apps/frontend/src/components/auditor/DataDeconstructor.tsx +85 -0
  104. package/apps/frontend/src/components/auditor/DecisionWall.tsx +65 -0
  105. package/apps/frontend/src/components/auditor/Dropzone.tsx +137 -0
  106. package/apps/frontend/src/components/common/Button.tsx +97 -0
  107. package/apps/frontend/src/components/common/Card.tsx +43 -0
  108. package/apps/frontend/src/components/common/Input.tsx +73 -0
  109. package/apps/frontend/src/components/common/Modal.tsx +53 -0
  110. package/apps/frontend/src/components/ui/Button.tsx +68 -0
  111. package/apps/frontend/src/components/ui/Card.tsx +86 -0
  112. package/apps/frontend/src/components/ui/Input.tsx +28 -0
  113. package/apps/frontend/src/components/ui/LogItem.tsx +64 -0
  114. package/apps/frontend/src/components/ui/MondayButton.tsx +40 -0
  115. package/apps/frontend/src/components/ui/MondayCard.tsx +24 -0
  116. package/apps/frontend/src/components/ui/Scouter.tsx +208 -0
  117. package/apps/frontend/src/components/ui/TerminalInput.tsx +202 -0
  118. package/apps/frontend/src/components/ui/Tooltip.tsx +67 -0
  119. package/apps/frontend/src/config/chatShortcuts.ts +20 -0
  120. package/apps/frontend/src/config/supabase.ts +6 -0
  121. package/apps/frontend/src/final_status.txt +3 -0
  122. package/apps/frontend/src/hooks/useScouter.ts +28 -0
  123. package/apps/frontend/src/hooks/useStoreHydration.ts +24 -0
  124. package/apps/frontend/src/improvement_status.txt +5 -0
  125. package/apps/frontend/src/index.css +88 -0
  126. package/apps/frontend/src/main.tsx +62 -0
  127. package/apps/frontend/src/monday_status.txt +7 -0
  128. package/apps/frontend/src/pages/ChatPage.tsx +201 -0
  129. package/apps/frontend/src/pages/DashboardPage.tsx +375 -0
  130. package/apps/frontend/src/pages/IntroWadi.tsx +114 -0
  131. package/apps/frontend/src/pages/LandingPage.tsx +103 -0
  132. package/apps/frontend/src/pages/Login.tsx +190 -0
  133. package/apps/frontend/src/pages/PrivacyPage.tsx +213 -0
  134. package/apps/frontend/src/pages/ProjectDetail.tsx +80 -0
  135. package/apps/frontend/src/pages/Projects.tsx +247 -0
  136. package/apps/frontend/src/pages/TermsPage.tsx +202 -0
  137. package/apps/frontend/src/router.tsx +83 -0
  138. package/apps/frontend/src/store/authStore.ts +152 -0
  139. package/apps/frontend/src/store/chatStore.ts +837 -0
  140. package/apps/frontend/src/store/documentStore.ts +89 -0
  141. package/apps/frontend/src/store/projectsStore.ts +111 -0
  142. package/apps/frontend/src/store/runsStore.ts +98 -0
  143. package/apps/frontend/src/utils/api.ts +34 -0
  144. package/apps/frontend/src/vite-env.d.ts +7 -0
  145. package/apps/frontend/tailwind.config.cjs +32 -0
  146. package/apps/frontend/tsconfig.app.json +27 -0
  147. package/apps/frontend/tsconfig.json +7 -0
  148. package/apps/frontend/tsconfig.node.json +26 -0
  149. package/apps/frontend/vite.config.ts +25 -0
  150. package/apps/kivo/.firebase/hosting.d3d3.cache +11 -0
  151. package/apps/kivo/.firebaserc +5 -0
  152. package/apps/kivo/BRANDING_GUIDE.md +71 -0
  153. package/apps/kivo/DEPLOYMENT_READY.md +46 -0
  154. package/apps/kivo/DEPLOY_URL.md +12 -0
  155. package/apps/kivo/IMPLEMENTATION_REPORT.md +35 -0
  156. package/apps/kivo/IMPLEMENTATION_REPORT_FINAL.md +49 -0
  157. package/apps/kivo/PLAN_MOBILE_2.0.md +44 -0
  158. package/apps/kivo/PWA_VERIFICATION_GUIDE.md +77 -0
  159. package/apps/kivo/README.md +28 -0
  160. package/apps/kivo/REBUILD_REPORT.md +34 -0
  161. package/apps/kivo/UPGRADE_REPORT.md +35 -0
  162. package/apps/kivo/android/app/build.gradle +54 -0
  163. package/apps/kivo/android/app/capacitor.build.gradle +19 -0
  164. package/apps/kivo/android/app/proguard-rules.pro +21 -0
  165. package/apps/kivo/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
  166. package/apps/kivo/android/app/src/main/AndroidManifest.xml +35 -0
  167. package/apps/kivo/android/app/src/main/java/com/kivo/app/MainActivity.java +5 -0
  168. package/apps/kivo/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  169. package/apps/kivo/android/app/src/main/res/drawable/splash.png +0 -0
  170. package/apps/kivo/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  171. package/apps/kivo/android/app/src/main/res/drawable-land-ldpi/splash.png +0 -0
  172. package/apps/kivo/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  173. package/apps/kivo/android/app/src/main/res/drawable-land-night-hdpi/splash.png +0 -0
  174. package/apps/kivo/android/app/src/main/res/drawable-land-night-ldpi/splash.png +0 -0
  175. package/apps/kivo/android/app/src/main/res/drawable-land-night-mdpi/splash.png +0 -0
  176. package/apps/kivo/android/app/src/main/res/drawable-land-night-xhdpi/splash.png +0 -0
  177. package/apps/kivo/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png +0 -0
  178. package/apps/kivo/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png +0 -0
  179. package/apps/kivo/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  180. package/apps/kivo/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  181. package/apps/kivo/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  182. package/apps/kivo/android/app/src/main/res/drawable-night/splash.png +0 -0
  183. package/apps/kivo/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  184. package/apps/kivo/android/app/src/main/res/drawable-port-ldpi/splash.png +0 -0
  185. package/apps/kivo/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  186. package/apps/kivo/android/app/src/main/res/drawable-port-night-hdpi/splash.png +0 -0
  187. package/apps/kivo/android/app/src/main/res/drawable-port-night-ldpi/splash.png +0 -0
  188. package/apps/kivo/android/app/src/main/res/drawable-port-night-mdpi/splash.png +0 -0
  189. package/apps/kivo/android/app/src/main/res/drawable-port-night-xhdpi/splash.png +0 -0
  190. package/apps/kivo/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png +0 -0
  191. package/apps/kivo/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png +0 -0
  192. package/apps/kivo/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  193. package/apps/kivo/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  194. package/apps/kivo/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  195. package/apps/kivo/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
  196. package/apps/kivo/android/app/src/main/res/layout/activity_main.xml +12 -0
  197. package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
  198. package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +9 -0
  199. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  200. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png +0 -0
  201. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  202. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  203. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher.png +0 -0
  204. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png +0 -0
  205. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png +0 -0
  206. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png +0 -0
  207. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  208. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png +0 -0
  209. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  210. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  211. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  212. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png +0 -0
  213. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  214. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  215. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  216. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png +0 -0
  217. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  218. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  219. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  220. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png +0 -0
  221. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  222. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  223. package/apps/kivo/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  224. package/apps/kivo/android/app/src/main/res/values/strings.xml +7 -0
  225. package/apps/kivo/android/app/src/main/res/values/styles.xml +22 -0
  226. package/apps/kivo/android/app/src/main/res/xml/file_paths.xml +5 -0
  227. package/apps/kivo/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
  228. package/apps/kivo/android/build.gradle +29 -0
  229. package/apps/kivo/android/capacitor.settings.gradle +3 -0
  230. package/apps/kivo/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  231. package/apps/kivo/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  232. package/apps/kivo/android/gradle.properties +22 -0
  233. package/apps/kivo/android/gradlew +252 -0
  234. package/apps/kivo/android/gradlew.bat +94 -0
  235. package/apps/kivo/android/settings.gradle +5 -0
  236. package/apps/kivo/android/variables.gradle +16 -0
  237. package/apps/kivo/assets/icon.png +0 -0
  238. package/apps/kivo/assets/splash.png +0 -0
  239. package/apps/kivo/capacitor.config.json +16 -0
  240. package/apps/kivo/firebase.json +6 -0
  241. package/apps/kivo/jest.config.js +4 -0
  242. package/apps/kivo/package.json +26 -0
  243. package/apps/kivo/tests_disabled/logic.test.js +34 -0
  244. package/apps/kivo/www/assets/icon-192.png +0 -0
  245. package/apps/kivo/www/assets/icon-512.png +0 -0
  246. package/apps/kivo/www/assets/kivo-icon.png +0 -0
  247. package/apps/kivo/www/assets/pop.mp3 +0 -0
  248. package/apps/kivo/www/favicon.ico +0 -0
  249. package/apps/kivo/www/firebase-config.js +75 -0
  250. package/apps/kivo/www/index.html +38 -0
  251. package/apps/kivo/www/manifest.json +29 -0
  252. package/apps/kivo/www/script.js +72 -0
  253. package/apps/kivo/www/style.css +82 -0
  254. package/apps/kivo/www/sw.js +46 -0
  255. package/apps/kivo-brain-api/.env.example +2 -0
  256. package/apps/kivo-brain-api/KIVO_BACKEND_SETUP.md +27 -0
  257. package/apps/kivo-brain-api/controllers/kivoController.js +31 -0
  258. package/apps/kivo-brain-api/index.js +24 -0
  259. package/apps/kivo-brain-api/package.json +17 -0
  260. package/apps/kivo-brain-api/routes/message.js +8 -0
  261. package/apps/kivo-brain-api/services/openaiService.js +64 -0
  262. package/apps/tests/wadi-tests.js +155 -0
  263. package/apps/wadi-brain/docs/README_REMOVE_OPTIONS.md +26 -0
  264. package/cli/commands/deploy.js +10 -0
  265. package/cli/commands/docs.js +16 -0
  266. package/cli/commands/explain.js +29 -0
  267. package/cli/commands/lint.js +14 -0
  268. package/cli/index.js +26 -0
  269. package/cli/package.json +12 -0
  270. package/docs/CNAME +1 -0
  271. package/docs/README.md +38 -0
  272. package/docs/USO.md +30 -0
  273. package/docs/index.html +46 -0
  274. package/eslint.config.js +101 -0
  275. package/final_validation.json +27 -0
  276. package/frontend_listado.txt +33387 -0
  277. package/listado.txt +12591 -0
  278. package/package.json +46 -0
  279. package/packages/logger/index.js +43 -0
  280. package/packages/logger/package.json +19 -0
  281. package/packages/logger/test-logger.js +7 -0
  282. package/packages_listado.txt +801 -0
  283. package/pnpm-workspace.yaml +6 -0
  284. package/reseteador_existencial.ps1 +40 -0
  285. package/scripts/bump-version.js +26 -0
  286. package/scripts/env.ps1 +13 -0
  287. package/scripts/setup_android.ps1 +25 -0
  288. package/scripts/setup_virtual_env.ps1 +77 -0
  289. package/scripts/smoke-test.js +84 -0
  290. package/scripts/trigger_render_deploy.ps1 +3 -0
  291. package/scripts/validate-release.js +34 -0
  292. package/temp_check.js +57 -0
  293. package/tsconfig.json +5 -0
  294. package/validation_report.json +27 -0
@@ -0,0 +1,656 @@
1
+ import { Router } from "express";
2
+ import { openai, AI_MODEL } from "./openai.js";
3
+ import { generateSystemPrompt, generateAuditPrompt } from "./wadi-brain.js";
4
+ import { supabase } from "./supabase.js";
5
+ import { AppError, AuthError, ModelError } from "./core/errors.js";
6
+ import {
7
+ validateChatInput,
8
+ validateProjectInput,
9
+ validateRunInput,
10
+ } from "./middleware/validation.js";
11
+ import { upload } from "./middleware/upload.js";
12
+ import { createRequire } from "module";
13
+ const require = createRequire(import.meta.url);
14
+ const pdf = require("pdf-parse");
15
+ import { wadiPreFlight } from "./layers/human_pattern/index.js";
16
+
17
+ const router = Router();
18
+
19
+ // Helper: Verify Auth Token via Supabase
20
+ const getAuthenticatedUser = async (req) => {
21
+ const authHeader = req.headers.authorization;
22
+ if (!authHeader) return null;
23
+ const token = authHeader.replace("Bearer ", "");
24
+
25
+ const {
26
+ data: { user },
27
+ error,
28
+ } = await supabase.auth.getUser(token);
29
+
30
+ if (error || !user) return null;
31
+ return user;
32
+ };
33
+
34
+ // Helper: Async Wrapper
35
+ const asyncHandler = (fn) => (req, res, next) => {
36
+ Promise.resolve(fn(req, res, next)).catch(next);
37
+ };
38
+
39
+ // Helper: Process attachments for OpenAI
40
+ const processAttachments = async (message, attachments) => {
41
+ if (!attachments || attachments.length === 0) return message;
42
+
43
+ // Si hay adjuntos, preparamos el contenido estructurado
44
+ const content = [{ type: "text", text: message }];
45
+
46
+ attachments.forEach((att) => {
47
+ const url = typeof att === "string" ? att : att.url;
48
+ if (url && (url.startsWith("data:image") || url.includes("supabase"))) {
49
+ content.push({ type: "image_url", image_url: { url } });
50
+ }
51
+ });
52
+
53
+ return content;
54
+ };
55
+
56
+ // Helper: Fetch Past Failures (Long Term Memory)
57
+ const fetchUserCriminalRecord = async (userId) => {
58
+ try {
59
+ const { data: audits } = await supabase
60
+ .from("messages")
61
+ .select("content, created_at")
62
+ .eq("user_id", userId)
63
+ .eq("role", "system")
64
+ .ilike("content", "[AUDIT_LOG_V1]%")
65
+ .order("created_at", { ascending: false })
66
+ .limit(3);
67
+
68
+ if (!audits || audits.length === 0) return [];
69
+ let failures = [];
70
+
71
+ for (const audit of audits) {
72
+ try {
73
+ const jsonPart = audit.content.replace("[AUDIT_LOG_V1]\n", "");
74
+ const parsed = JSON.parse(jsonPart);
75
+ const dateStr = new Date(audit.created_at).toISOString().split("T")[0];
76
+ const highRisk = (parsed.vulnerabilities || [])
77
+ .filter((v) => v.level === "HIGH")
78
+ .map((v) => `${v.title} (${dateStr})`);
79
+ failures = [...failures, ...highRisk];
80
+ } catch (e) {
81
+ console.error("Memory parse error", e);
82
+ }
83
+ }
84
+ return [...new Set(failures)].slice(0, 3);
85
+ } catch (err) {
86
+ return [];
87
+ }
88
+ };
89
+
90
+ const calculateRank = (points) => {
91
+ if (points >= 801) return "ENTIDAD_DE_ORDEN";
92
+ if (points >= 401) return "ESTRATEGA_JUNIOR";
93
+ if (points >= 101) return "CIVIL_PROMEDIO";
94
+ return "GENERADOR_DE_HUMO";
95
+ };
96
+
97
+ // --- ROUTES ---
98
+
99
+ router.get(
100
+ "/user/criminal-summary",
101
+ asyncHandler(async (req, res) => {
102
+ const user = await getAuthenticatedUser(req);
103
+ if (!user) throw new AuthError("Authentication required");
104
+
105
+ const { data: audits } = await supabase
106
+ .from("messages")
107
+ .select("content")
108
+ .eq("user_id", user.id)
109
+ .eq("role", "system")
110
+ .ilike("content", "[AUDIT_LOG_V1]%");
111
+
112
+ let totalHighRisks = 0;
113
+ if (audits) {
114
+ audits.forEach((audit) => {
115
+ try {
116
+ const parsed = JSON.parse(
117
+ audit.content.replace("[AUDIT_LOG_V1]\n", "")
118
+ );
119
+ totalHighRisks += (parsed.vulnerabilities || []).filter(
120
+ (v) => v.level === "HIGH"
121
+ ).length;
122
+ } catch (e) {}
123
+ });
124
+ }
125
+ res.json({ totalAudits: audits?.length || 0, totalHighRisks });
126
+ })
127
+ );
128
+
129
+ router.post(
130
+ "/user/admit-failure",
131
+ asyncHandler(async (req, res) => {
132
+ const user = await getAuthenticatedUser(req);
133
+ if (!user) throw new AuthError("Authentication required");
134
+
135
+ const { data: profile } = await supabase
136
+ .from("profiles")
137
+ .select("efficiency_points")
138
+ .eq("id", user.id)
139
+ .maybeSingle();
140
+ const newPoints = profile?.efficiency_points || 0; // No penalty, just reset state
141
+ const newRank = calculateRank(newPoints);
142
+
143
+ await supabase.from("profiles").upsert({
144
+ id: user.id,
145
+ active_focus: null,
146
+ efficiency_points: newPoints,
147
+ efficiency_rank: newRank,
148
+ updated_at: new Date().toISOString(),
149
+ });
150
+
151
+ res.json({
152
+ reply:
153
+ "Está bien. A veces el plan se rompe y lo más inteligente es soltarlo antes de que nos hunda a los dos. Perdimos un poco de impulso, pero recuperamos la claridad. Borrón y cuenta nueva. ¿Qué tenemos en la cabeza ahora?",
154
+ efficiencyPoints: newPoints,
155
+ efficiencyRank: newRank,
156
+ });
157
+ })
158
+ );
159
+
160
+ // In-memory store for guest sessions (Volatile)
161
+ const guestSessions = new Map();
162
+
163
+ router.post(
164
+ "/chat",
165
+ validateChatInput,
166
+ asyncHandler(async (req, res) => {
167
+ let user = await getAuthenticatedUser(req);
168
+
169
+ // Guest Mode: If no user, user stays null, but we proceed carefully.
170
+ const {
171
+ message,
172
+ conversationId,
173
+ mode,
174
+ explainLevel,
175
+ topic,
176
+ attachments,
177
+ isMobile,
178
+ customSystemPrompt,
179
+ } = req.body;
180
+
181
+ let currentConversationId = conversationId;
182
+ let history = [];
183
+ let profile = {
184
+ efficiency_rank: "VISITANTE",
185
+ efficiency_points: 0,
186
+ active_focus: null,
187
+ };
188
+ let pastFailures = [];
189
+
190
+ // --- CASE A: AUTHENTICATED USER ---
191
+ if (user) {
192
+ if (!currentConversationId) {
193
+ const { data: newConv } = await supabase
194
+ .from("conversations")
195
+ .insert([
196
+ {
197
+ user_id: user.id,
198
+ title: message.substring(0, 60),
199
+ mode: mode || "normal",
200
+ explain_level: explainLevel || "normal",
201
+ },
202
+ ])
203
+ .select()
204
+ .single();
205
+ currentConversationId = newConv.id;
206
+ }
207
+
208
+ await supabase.from("messages").insert({
209
+ conversation_id: currentConversationId,
210
+ user_id: user.id,
211
+ role: "user",
212
+ content: message,
213
+ attachments: attachments || [],
214
+ });
215
+
216
+ const { data: dbHistory } = await supabase
217
+ .from("messages")
218
+ .select("role, content")
219
+ .eq("conversation_id", currentConversationId)
220
+ .order("created_at", { ascending: true });
221
+ history = dbHistory || [];
222
+
223
+ // [SAFETY]: If DB read missed the insert (race condition), manually add current message
224
+ if (history.length === 0) {
225
+ history.push({ role: "user", content: message });
226
+ }
227
+
228
+ const { data: dbProfile } = await supabase
229
+ .from("profiles")
230
+ .select("*")
231
+ .eq("id", user.id)
232
+ .maybeSingle();
233
+ if (dbProfile) profile = dbProfile;
234
+
235
+ pastFailures = await fetchUserCriminalRecord(user.id);
236
+ }
237
+ // --- CASE B: GUEST MODE (IN-MEMORY) ---
238
+ else {
239
+ if (!currentConversationId) {
240
+ currentConversationId = `guest-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
241
+ guestSessions.set(currentConversationId, []);
242
+ }
243
+
244
+ if (!guestSessions.has(currentConversationId)) {
245
+ guestSessions.set(currentConversationId, []);
246
+ }
247
+ history = guestSessions.get(currentConversationId);
248
+
249
+ // Add User Message to Memory
250
+ history.push({ role: "user", content: message });
251
+
252
+ profile = {
253
+ efficiency_rank: "VISITANTE_CURIOSO",
254
+ efficiency_points: 0,
255
+ active_focus: null,
256
+ };
257
+ }
258
+
259
+ // --- HUMAN PATTERN LAYER (WADI V1) ---
260
+ // WADI responde desde experiencia acumulada, no desde definiciones abstractas.
261
+ // Si puede describir un patrón humano, no explica teoría.
262
+
263
+ // --- HUMAN PATTERN LAYER (WADI V1) ---
264
+ // SKIP IF PANIC MODE
265
+ if (mode !== "panic") {
266
+ const preFlightData = wadiPreFlight(message);
267
+
268
+ if (preFlightData) {
269
+ console.log(`[WADI HUMAN LAYER] Response sent. HALTING.`);
270
+ return res.json({
271
+ reply: preFlightData.reply,
272
+ detectedPattern: preFlightData.pattern,
273
+ conversationId: currentConversationId,
274
+ efficiencyPoints: profile.efficiency_points,
275
+ });
276
+ }
277
+ }
278
+
279
+ // --- COMMON: GENERATE AI RESPONSE ---
280
+
281
+ // [FIX]: Remove current msg from history for prompt context, and limit to recent history
282
+ const previousHistory = history.slice(0, -1).slice(-20);
283
+ const messageCount = history.length - 1; // Correct count of previous messages
284
+
285
+ const fullSystemPrompt = generateSystemPrompt(
286
+ mode || "normal",
287
+ topic || "general",
288
+ explainLevel || "normal",
289
+ {}, // sessionPrefs
290
+ "hostile",
291
+ isMobile,
292
+ messageCount,
293
+ pastFailures,
294
+ profile.efficiency_rank,
295
+ profile.efficiency_points,
296
+ profile.active_focus,
297
+ req.body.memory || {} // Pass memory
298
+ );
299
+
300
+ // [DEBUG OVERRIDE]
301
+ const finalSystemPrompt = customSystemPrompt || fullSystemPrompt;
302
+
303
+ const userContent = await processAttachments(message, attachments);
304
+
305
+ // Prepare OpenAI Messages: System + Previous History + Current User Message
306
+ const openAIHistory = previousHistory.map((m) => ({
307
+ role: m.role,
308
+ content: m.content,
309
+ }));
310
+
311
+ try {
312
+ console.log(
313
+ `[AI START] Calling model for conv ${currentConversationId}...`
314
+ );
315
+
316
+ // 3. TIMEOUT SAFETY (25s)
317
+ const timeoutMs = 25000;
318
+ const controller = new AbortController();
319
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
320
+
321
+ const completion = await Promise.race([
322
+ openai.chat.completions.create({
323
+ model: AI_MODEL,
324
+ messages: [
325
+ { role: "system", content: finalSystemPrompt },
326
+ ...openAIHistory,
327
+ { role: "user", content: userContent },
328
+ ],
329
+ }),
330
+ new Promise((_, reject) =>
331
+ setTimeout(() => reject(new Error("AI_TIMEOUT")), timeoutMs)
332
+ ),
333
+ ]);
334
+ clearTimeout(timeoutId);
335
+
336
+ console.log(`[AI END] Response received.`);
337
+
338
+ const reply = completion.choices[0].message.content;
339
+
340
+ let newPoints = profile.efficiency_points;
341
+ let newRank = profile.efficiency_rank;
342
+ let systemDeath = false;
343
+
344
+ // Persistence Update
345
+ if (user) {
346
+ let pointChange = 0;
347
+ if (reply.includes("[FOCO_LIBERADO]")) {
348
+ pointChange = 20;
349
+ } else if (reply.includes("[FORCE_DECISION]")) {
350
+ pointChange = -10;
351
+ } else if (reply.includes("[DECONSTRUCT_START]")) {
352
+ // Smoke Index: Penalty for excessive noise
353
+ const noiseCount = (reply.match(/"category":\s*"RUIDO"/g) || [])
354
+ .length;
355
+ if (noiseCount > 3) pointChange = -10;
356
+ }
357
+ newPoints += pointChange;
358
+ systemDeath = newPoints <= -50;
359
+
360
+ if (systemDeath) {
361
+ await supabase.from("messages").delete().eq("user_id", user.id);
362
+ await supabase.from("conversations").delete().eq("user_id", user.id);
363
+ newPoints = 0;
364
+ }
365
+
366
+ newRank = calculateRank(newPoints);
367
+
368
+ await supabase.from("profiles").upsert({
369
+ id: user.id,
370
+ efficiency_points: newPoints,
371
+ efficiency_rank: newRank,
372
+ active_focus: reply.includes("[FOCO_LIBERADO]")
373
+ ? null
374
+ : profile.active_focus,
375
+ updated_at: new Date().toISOString(),
376
+ });
377
+
378
+ if (!systemDeath) {
379
+ await supabase.from("messages").insert({
380
+ conversation_id: currentConversationId,
381
+ user_id: user.id,
382
+ role: "assistant",
383
+ content: reply,
384
+ });
385
+ }
386
+ } else {
387
+ guestSessions
388
+ .get(currentConversationId)
389
+ .push({ role: "assistant", content: reply });
390
+ }
391
+
392
+ res.json({
393
+ reply: systemDeath ? "SYSTEM FAILURE" : reply,
394
+ conversationId: currentConversationId,
395
+ efficiencyPoints: newPoints,
396
+ efficiencyRank: newRank,
397
+ systemDeath,
398
+ isGuest: !user,
399
+ });
400
+ } catch (error) {
401
+ console.error("[AI ERROR]:", error);
402
+ let errorReply =
403
+ "El sistema colapsó por su propia complejidad. O OpenAI está caído. Probá de nuevo.";
404
+ if (error.message === "AI_TIMEOUT") {
405
+ errorReply =
406
+ "El modelo se quedó pensando demasiado. Tu pregunta debe ser fascinante o terriblemente aburrida. Simplificala.";
407
+ }
408
+
409
+ // Return a safe fallback instead of hanging
410
+ res.json({
411
+ reply: errorReply,
412
+ conversationId: currentConversationId,
413
+ efficiencyPoints: profile.efficiency_points,
414
+ });
415
+ }
416
+ })
417
+ );
418
+
419
+ // --- PROYECTOS (Simplificados) ---
420
+ router.get(
421
+ "/projects",
422
+ asyncHandler(async (req, res) => {
423
+ const user = await getAuthenticatedUser(req);
424
+ const { data } = await supabase
425
+ .from("projects")
426
+ .select("*")
427
+ .eq("user_id", user.id)
428
+ .order("created_at", { ascending: false });
429
+ res.json(data);
430
+ })
431
+ );
432
+
433
+ router.post(
434
+ "/projects",
435
+ validateProjectInput,
436
+ asyncHandler(async (req, res) => {
437
+ const user = await getAuthenticatedUser(req);
438
+ const { data } = await supabase
439
+ .from("projects")
440
+ .insert([{ ...req.body, user_id: user.id }])
441
+ .select()
442
+ .single();
443
+ res.json(data);
444
+ })
445
+ );
446
+
447
+ // Helper: Generate Technical Project Name
448
+ const generateProjectName = async (description) => {
449
+ try {
450
+ const completion = await openai.chat.completions.create({
451
+ model: AI_MODEL,
452
+ messages: [
453
+ {
454
+ role: "system",
455
+ content:
456
+ "Generá un NOMBRE TÉCNICO ÚNICO (Max 35 chars) para este proyecto. Uppercase, snake_case. Sin extensión de archivo. EJ: SISTEMA_LOGISTICA_V1, RED_NEURONAL_BASE.",
457
+ },
458
+ { role: "user", content: description.substring(0, 500) },
459
+ ],
460
+ max_tokens: 20,
461
+ });
462
+ let name = completion.choices[0].message.content.trim();
463
+ // Sanitize
464
+ name = name.replace(/[^A-Z0-9_]/g, "_").replace(/_{2,}/g, "_");
465
+ return name;
466
+ } catch (e) {
467
+ return `PROYECTO_${Date.now()}`;
468
+ }
469
+ };
470
+
471
+ router.post(
472
+ "/projects/crystallize",
473
+ asyncHandler(async (req, res) => {
474
+ const user = await getAuthenticatedUser(req);
475
+ if (!user) throw new AuthError("Authentication required");
476
+
477
+ let { name, description } = req.body;
478
+
479
+ if (!description || description.trim().length === 0) {
480
+ throw new AppError(
481
+ "MONDAY_REJECTION",
482
+ "No puedo cristalizar la nada misma. Escribí una descripción."
483
+ );
484
+ }
485
+
486
+ // Auto-generate name if missing
487
+ if (!name || name.trim().length === 0) {
488
+ name = await generateProjectName(description);
489
+ }
490
+
491
+ const { data, error } = await supabase
492
+ .from("projects")
493
+ .insert([
494
+ {
495
+ user_id: user.id,
496
+ name: name,
497
+ description: description,
498
+ status: "PLANNING",
499
+ },
500
+ ])
501
+ .select()
502
+ .single();
503
+
504
+ if (error) throw new AppError("DB_ERROR", error.message);
505
+
506
+ res.json(data);
507
+ })
508
+ );
509
+
510
+ router.delete(
511
+ "/projects/:id",
512
+ asyncHandler(async (req, res) => {
513
+ const user = await getAuthenticatedUser(req);
514
+ await supabase
515
+ .from("projects")
516
+ .delete()
517
+ .eq("id", req.params.id)
518
+ .eq("user_id", user.id);
519
+ res.json({ success: true });
520
+ })
521
+ );
522
+
523
+ // Conversations list
524
+ router.get(
525
+ "/conversations",
526
+ asyncHandler(async (req, res) => {
527
+ const user = await getAuthenticatedUser(req);
528
+ const { data } = await supabase
529
+ .from("conversations")
530
+ .select("*")
531
+ .eq("user_id", user.id)
532
+ .order("updated_at", { ascending: false });
533
+ res.json(data);
534
+ })
535
+ );
536
+
537
+ router.delete(
538
+ "/conversations/:id",
539
+ asyncHandler(async (req, res) => {
540
+ const user = await getAuthenticatedUser(req);
541
+ await supabase
542
+ .from("conversations")
543
+ .delete()
544
+ .eq("id", req.params.id)
545
+ .eq("user_id", user.id);
546
+ res.json({ success: true });
547
+ })
548
+ );
549
+
550
+ // 1.6 Get Single Conversation (with messages)
551
+ router.get(
552
+ "/conversations/:id",
553
+ asyncHandler(async (req, res) => {
554
+ const user = await getAuthenticatedUser(req);
555
+ if (!user) throw new AuthError("Authentication required");
556
+ const { id } = req.params;
557
+
558
+ // Obtener metadatos de la conversación
559
+ const { data: conversation, error: convError } = await supabase
560
+ .from("conversations")
561
+ .select("*")
562
+ .eq("id", id)
563
+ .eq("user_id", user.id)
564
+ .single();
565
+
566
+ if (convError || !conversation) {
567
+ throw new AppError("NOT_FOUND", "Conversación no encontrada", 404); // Assuming AppError handles this signature or existing error handler does
568
+ }
569
+
570
+ // Obtener los mensajes vinculados
571
+ const { data: messages, error: msgError } = await supabase
572
+ .from("messages")
573
+ .select("*")
574
+ .eq("conversation_id", id)
575
+ .order("created_at", { ascending: true });
576
+
577
+ if (msgError) throw new AppError("DB_ERROR", msgError.message);
578
+
579
+ res.json({ ...conversation, messages });
580
+ })
581
+ );
582
+
583
+ // ------------------------------------------------------------------
584
+ // DOCUMENT INTAKE (Intake & RAG Phase 1)
585
+ // ------------------------------------------------------------------
586
+ router.post(
587
+ "/documents/upload",
588
+ upload.single("file"),
589
+ asyncHandler(async (req, res) => {
590
+ const user = await getAuthenticatedUser(req);
591
+ // if (!user) throw new AuthError("Identifíquese antes de subir basura."); // Optional strict auth
592
+
593
+ if (!req.file) {
594
+ throw new AppError("No enviaste ningún archivo. ¿Es una broma?", 400);
595
+ }
596
+
597
+ let textContent = "";
598
+
599
+ try {
600
+ if (req.file.mimetype === "application/pdf") {
601
+ const data = await pdf(req.file.buffer);
602
+ textContent = data.text;
603
+ } else {
604
+ // Text / Markdown
605
+ textContent = req.file.buffer.toString("utf-8");
606
+ }
607
+ } catch (e) {
608
+ console.error("Error parsing document:", e);
609
+ throw new AppError(
610
+ "No pude leer ese archivo. Probablemente esté corrupto como tu moral.",
611
+ 422
612
+ );
613
+ }
614
+
615
+ // Clean text lightly
616
+ textContent = textContent.replace(/\s+/g, " ").trim();
617
+
618
+ // Check token count estimation (rough)
619
+ const estimatedTokens = textContent.length / 4;
620
+
621
+ res.json({
622
+ filename: req.file.originalname,
623
+ content: textContent,
624
+ size: req.file.size,
625
+ tokens: Math.round(estimatedTokens),
626
+ message: "Archivo procesado. Si esperabas un premio, seguí esperando.",
627
+ });
628
+ })
629
+ );
630
+
631
+ // [DEBUG] Get Current System Prompt
632
+ router.post(
633
+ "/debug/system-prompt",
634
+ asyncHandler(async (req, res) => {
635
+ const { mode, topic, explainLevel, isMobile, messageCount } = req.body;
636
+
637
+ // Generate prompt with mock or provided data
638
+ const prompt = generateSystemPrompt(
639
+ mode || "normal",
640
+ topic || "general",
641
+ explainLevel || "normal",
642
+ {},
643
+ "hostile",
644
+ isMobile || false,
645
+ messageCount || 0,
646
+ [], // pastFailures mocked
647
+ "GENERADOR_DE_HUMO", // rank mocked
648
+ 0, // points mocked
649
+ null // active_focus mocked
650
+ );
651
+
652
+ res.json({ prompt });
653
+ })
654
+ );
655
+
656
+ export default router;
@@ -0,0 +1,17 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+ import dotenv from "dotenv";
3
+ dotenv.config({ path: "../../.env" });
4
+
5
+ const supabaseUrl = process.env.SUPABASE_URL;
6
+ const supabaseKey = process.env.SUPABASE_KEY;
7
+
8
+ if (!supabaseUrl || !supabaseKey) {
9
+ console.warn(
10
+ "⚠️ Missing Supabase URL or Key. functionality will be limited."
11
+ );
12
+ }
13
+
14
+ export const supabase = createClient(
15
+ supabaseUrl || "https://placeholder.supabase.co",
16
+ supabaseKey || "placeholder"
17
+ );