Commit Graph
23 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 5 5b78f6677c feat(umind): el cliente administra sus agentes desde el portal
Acá es donde uMind deja de ser una herramienta interna: el cliente entra
a /portal/studio con su sesión de portal y gestiona lo suyo.

- UmindScopePortal/UmindScopeStaff es el ÚNICO punto donde se decide el
  alcance. El del cliente sale de GetClienteIDsForPortalUser, el mismo
  que ya autoriza el resto del portal. nil = staff sin restricción,
  slice vacío = no ve nada; una ruta sin scope también cae en "no ve
  nada" para que olvidarse el middleware falle visible y no abra todo.
- Un solo set de handlers montado bajo /app/umind y /portal/umind
  (RegistrarRutasUmind). Duplicarlos sería duplicar las chances de
  olvidar un chequeo.
- Guarda de acceso en TODOS los handlers, incluidos los sub-recursos que
  llegan por :id (documento, tool, canal, conexión): hay que cargarlos
  para saber de quién son, si no un cliente podría borrar el canal de
  otro adivinando el id. Responden 404, no 403: un 403 confirmaría que
  el recurso existe.
- Cierra un bug preexistente: las lecturas GET /app/umind/* no tenían
  SoloAdmin ni pasaban por MenuMiddleware, así que cualquier usuario de
  staff podía leer los tenants de todos los clientes.
- Límite de agentes por plan (409 con mensaje claro). Un tenant sin plan
  no tiene límite: cortarles de golpe sería peor que dejarlos como estaban.
- /umind/ai-configs reemplaza con alcance a /app/api/ai-config/select,
  que devolvía TODAS las configs del sistema.
- El SPA deduce por la URL si es staff o cliente (base del router,
  prefijo de API y URL de login) y oculta lo que es solo de staff.
- Test de aislamiento entre clientes: 7 casos, incluido que un scope
  vacío no se confunda con staff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:51:12 -05:00
Lizandro GuarnizoandClaude Sonnet 5 f3f2f421d6 feat: uMind pasa a multi-agente por tenant
Un tenant (negocio/sitio, dueño de los dominios permitidos) puede tener
varios UmindAgente independientes (ej. "Ventas", "Soporte"), cada uno con
su propia config de IA, tono, base de conocimiento, tools, canales y
conexión de correo. El site_key también pasa a ser por agente, así cada
uno tiene su propio <script> de widget embebible y su propio color.

Backend:
- Nuevo modelo UmindAgente (pkg/models/umind_agente.go), con SiteKey,
  AiConfigID, Tono, MensajeBienvenida y Color — campos que antes vivían en
  UmindTenant y se sacan de ahí (las columnas viejas quedan huérfanas sin
  usar, no se hace DROP COLUMN).
- UmindDocumento, UmindChunk, UmindHerramienta, UmindCanal, UmindConexion y
  UmindMensaje pasan de TenantID a AgenteID. El campo se agrega sin
  "not null" para no romper el ALTER TABLE en Postgres sobre tablas que ya
  tienen filas (ej. emetropolitana).
- migrations.MigrarUmindAgentes(): idempotente, crea un agente "Principal"
  por cada tenant existente heredando lo que ya tenía configurado, y mueve
  sus datos de tenant_id a agente_id. Corre en cada arranque normal, mismo
  criterio que los Seed* — nada se rompe para los tenants ya en producción.
- Motor del agente, widget, canales (Telegram/WhatsApp) y OAuth de correo
  ahora operan sobre UmindAgente; el tenant solo se consulta para el
  chequeo de dominio permitido y el nombre del negocio que ve el visitante.

Frontend: nueva jerarquía de navegación tenant → lista de agentes
(TenantAgentes.vue) → detalle de un agente (AgenteDetail.vue, antes
TenantDetail.vue) con las mismas 6 tabs de siempre, ahora por agente. El
modal de tenant en el sidebar se achica a nombre/dominios/activo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:30:06 -05:00
Lizandro GD 40dfaf5773 Agrega API Keys scoped (token + IP obligatoria + alcance) para /api/v2
Alternativa al ADMIN_API_KEY único de entorno, que sigue funcionando
como llave maestra para no romper integraciones existentes.

- Modelo ApiKey: token hasheado, IP/CIDR obligatoria (fail-closed sin
  IP), scopes habilitados, último uso (fecha + IP).
- AdminApiAuth() acepta ahora tanto la llave maestra como una ApiKey;
  nuevo middleware RequireScope(scope) para gatear grupos de rutas.
- Fase 1: scopes aplicados a lo más sensible — oss (archivos),
  query_runner (SQL arbitrario), usuarios (usuarios/roles/módulos),
  pasarelas (credenciales de pago). El resto de /api/v2 sigue con
  la llave maestra hasta una fase 2.
- Panel /app/api-keys: crear/editar/revocar, token visible solo al
  crear, scopes por checkbox, IP obligatoria.
2026-08-10 16:33:57 +00:00
Lizandro GD 5ee5880dc2 Agrega uMind: chat con IA embebible por tenant (F1 — widget web + RAG)
Multi-tenant dentro de soft_usite, reutilizando la infraestructura ya
existente (AiConfig, motor de function-calling del agente de Telegram)
en vez de un servicio nuevo aparte:

- UmindTenant: sitio/cliente con dominios permitidos, config de IA para
  el chat y personalidad/tono.
- Ingesta: crawler simple (mismo dominio, N páginas) + chunking +
  embeddings (config global con módulo "umind_embeddings", pensada
  para OpenAI ya que Claude no ofrece embeddings) guardados como JSON,
  con búsqueda por similitud coseno en memoria (sin pgvector todavía).
- Agente acotado: única herramienta buscar_conocimiento, sin acceso a
  nada interno — si no encuentra la respuesta, lo dice en vez de
  inventar.
- Widget público (/widget/umind.js + /widget/:site_key/*), autenticado
  por site_key + validación de dominio (Origin/Referer), no por
  secreto, ya que la key viaja en el HTML público del sitio instalado.
- Panel /app/umind: tenants, estado de ingesta, historial de
  conversaciones por sesión.
2026-08-07 16:18:16 +00:00
Lizandro GD e5849549a0 Agrega API de pagos externos: tokens por servicio, atados a pasarela e IP
Permite emitir credenciales (token hasheado + IP/CIDR opcional) desde
/app/pagos-externos para que aplicaciones de terceros pidan cobros a
través de Bold/dLocal/PayPal sin acceso a nada más del sistema:

- POST /api/v1/pagos-externos/solicitar genera el link de cobro real
  usando solo las pasarelas habilitadas para ese servicio.
- Los webhooks existentes de Bold/dLocal/PayPal (firma obligatoria,
  idempotentes) ahora también resuelven referencias "extpay-…" sin
  tocar el flujo de contratos ("contrato-{id}").
- Al confirmarse el pago se notifica por webhook firmado (HMAC) y/o
  Telegram, configurable por servicio.
- CRUD de servicios protegido con SoloAdmin; token y callback_secret
  solo se muestran una vez, en DB se guardan hasheados.
2026-08-04 14:16:13 +00:00
Lizandro GDandClaude Opus 5 c8e5afceb2 fix: seguridad de pagos y accesos, integración PayPal y Coolify ampliado
Seguridad (crítico):
- Los webhooks de Bold y dLocal solo validaban la firma si el atacante la
  enviaba: sin cabecera se aceptaba cualquier payload. Ahora es obligatoria.
- GET /pago-exitoso marcaba contratos como pagados leyendo un query param del
  navegador. Ahora solo muestra estado; la confirmación la hace la verificación
  contra la API de la pasarela o el webhook firmado.
- /uploads se servía como estático público: se descargaban RUTs, facturas y
  entregables sabiendo la ruta. Ahora exige sesión.
- Los secretos JWT no se podían sobreescribir por entorno (faltaba el tag env:)
  y su valor estaba en el repo, permitiendo firmarse una sesión de admin. Ahora
  son configurables y el arranque se detiene si siguen con el valor publicado.
- .env y session.db salen del control de versiones.
- Query Runner, gestión de usuarios/roles/módulos y seeds quedan restringidos a
  administradores; antes bastaba con tener sesión.

Pasarelas de pago:
- dLocal generaba enlaces que nunca se reconciliaban: mandaba el ID numérico en
  vez de "contrato-N", la URL de retorno apuntaba a la API de dLocal y nunca se
  enviaba notification_url, así que su webhook jamás se disparaba.
- PayPal solo tenía pantalla de configuración. Se implementa el servicio
  completo (OAuth, orden, captura, verificación de webhook) y queda
  seleccionable como pasarela.
- La moneda estaba fija en COP: un contrato en USD generaba un cobro por esa
  cifra en pesos.

Contratos:
- pago_confirmado nunca volvía a false, así que el segundo ciclo de renovación
  no se cobraba aunque el cliente pagara. Se reinicia al generar enlace nuevo.
- Los contratos vencidos nunca cambiaban de estado y recibían correo a diario
  de forma indefinida; ahora se cierran tras 30 días de gracia.

Otros:
- Coolify: coolifyCall ignoraba el status HTTP y reportaba errores como éxito.
  El agente pasa de 10 a cobertura completa (servicios, bases de datos,
  variables de entorno, proyectos, equipos y recursos de servidor).
- SeedBalanceData ya no corre en cada arranque (recreaba transacciones
  borradas); ahora se invoca con SEED_BALANCE=1.
- Los seeds dejan de devolver permisos revocados en cada despliegue.
- Timeouts en las llamadas HTTP a Telegram y dLocal que podían colgarse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 03:20:00 +00:00
Lizandro GDandClaude Opus 4.6 9fd0772e91 refactor: rename Hermes API to Admin API v2 under /api/v2
Integrates the external API into the existing /api group as v2 with
API key auth (ADMIN_API_KEY), replacing the separate /hermes namespace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-13 12:17:08 +00:00
Lizandro GDandClaude Sonnet 5 6ccdbd93c8 feat: API Hermes con acceso total para consumo externo (API Key auth)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 02:50:31 +00:00
Lizandro Guarnizo 9a3a5d4b25 feat: pagina principal configurable por rol (HomeUrl) 2026-07-06 23:05:02 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e937676160 fix(auth): flujo de recuperación de contraseña usa email en vez de usuario
- login.html: corregir link roto /password/reset → /request-password-reset
- request-password-reset.html: reescribir — pide email, AJAX, diseño de la app
- RequestPasswordResetPost: buscar por email (GetUserByEmail), no revela si
  el correo existe o no (misma respuesta siempre — seguridad)
- password-reset.html: reescribir — eliminar campo nombre_usuario redundante,
  validación de contraseñas en cliente, diseño consistente con el resto del app
- PasswordResetPost: usar c.Locals("email") del token (ya validado por
  middleware) en vez de nombre_usuario del form
- password_reset.go middleware: ampliar expiración de token de 5 min a 1 hora

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 22:17:38 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d95e82034a fix: excluir /api/sms/send del middleware AuthApi
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 08:05:31 -05:00
Lizandro Guarnizo 2b7458b612 up 2026-05-16 20:01:36 -05:00
Lizandro Guarnizo 1b22cc2fe7 k 2026-05-16 15:26:48 -05:00
Lizandro Guarnizo 76a0c28b16 up 2026-05-16 13:44:32 -05:00
Lizandro Guarnizo 750e3172ff Update login.go 2026-05-15 16:05:27 -05:00
Lizandro Guarnizo f7e8d273d2 up 2026-05-15 15:47:49 -05:00
Lizandro Guarnizo 2377371b2c up 2026-05-14 22:52:10 -05:00
Lizandro Guarnizo 7f3fdfb4be up 2026-05-04 22:22:25 -05:00
Lizandro Guarnizo f787821444 up 2026-04-29 23:36:30 -05:00
Lizandro Guarnizo 9d4c4dba65 up 2026-04-29 23:01:58 -05:00
root fd739f8675 Descripción de los cambios 2025-04-24 02:07:47 +00:00
Lizandro Guarnizo 79d6cca94d qr vcard 2025-04-18 07:38:32 -05:00
Lizandro Guarnizo 6d8f1bcd6f Initial commit 2025-02-06 14:22:29 -05:00