Files
FRASA-CAS/api-gateway/src/config/index.ts
Consult AS Technology af2fb10956 feat(mi-dia): vendedores solo-app con cuenta Monday compartida
- Las listas personales de Mi día (leads, oportunidades, apartados) se
  calculan por captura local (LeadCaptura/Cliente + mapping
  LeadOportunidad) ADEMÁS de la columna people de Monday: la app ya es
  totalmente usable sin cuenta Monday
- Config MONDAY_VENDEDOR_COMPARTIDO_ID: cuando el usuario está ligado a
  la cuenta compartida, la columna people se ignora para 'lo mío' (si
  no, todos verían los leads de todos)
- scripts/vincular-vendedores-compartido.js: vincula todos los usuarios
  con rol vendedor a la cuenta Monday compartida
- Tests: 478 API, verde; verificado en vivo con usuario sin Monday
2026-09-05 05:37:52 +00:00

97 lines
4.6 KiB
TypeScript

import path from 'node:path'
import dotenv from 'dotenv'
// Carga .env local si existe y luego el .env raíz del ecosistema (no pisa vars ya definidas)
dotenv.config({ path: [path.resolve(process.cwd(), '.env'), path.resolve(process.cwd(), '..', '.env')] })
function env(name: string, fallback?: string): string {
const v = process.env[name] ?? fallback
if (v === undefined) throw new Error(`Falta la variable de entorno ${name}`)
return v
}
export const config = {
port: Number(env('PORT', '3000')),
nodeEnv: env('NODE_ENV', 'development'),
databaseUrl: env('DATABASE_URL', 'file:./dev.db'),
uploadsDir: path.resolve(process.cwd(), env('UPLOADS_DIR', './uploads')),
// Token compartido para llamadas internas script→API corriendo en este mismo
// host (sync de estatus → POST /api/cotizaciones/alertas-inventario). Vacío =
// desactivado: el endpoint exige JWT de usuario como siempre.
internalSyncToken: env('INTERNAL_SYNC_TOKEN', ''),
monday: {
apiKey: env('MONDAY_API_KEY'),
apiUrl: env('MONDAY_API_URL', 'https://api.monday.com/v2'),
apiVersion: '2024-10',
workspace: env('MONDAY_WORKSPACE', ''),
boards: {
contactos: env('MONDAY_BOARD_CONTACTOS'),
desarrollos: env('MONDAY_BOARD_DESARROLLOS'),
unidades: env('MONDAY_BOARD_UNIDADES'),
leads: env('MONDAY_BOARD_LEADS'),
oportunidades: env('MONDAY_BOARD_OPORTUNIDADES'),
apartado: env('MONDAY_BOARD_APARTADO'),
ventas: env('MONDAY_BOARD_VENTAS'),
// Tableros creados por scripts/setup-crm.ts; '' = aún no configurado
motivos: env('MONDAY_BOARD_MOTIVOS', ''),
actividades: env('MONDAY_BOARD_ACTIVIDADES', ''),
},
},
automatizaciones: {
// 'false' desactiva los crons (útil en tests/CI); los endpoints admin siguen vivos
enabled: env('MONDAY_AUTOMATIONS_ENABLED', 'true') !== 'false',
// 'false' desactiva la creación de actividades en Monday (default true; false en tests)
actividadesEnabled: env('ACTIVIDADES_ENABLED', 'true') !== 'false',
// IDs de usuario Monday separados por coma; vacío = usar el equipo rrEquipo
rrUserIds: env('MONDAY_RR_USER_IDS', ''),
// Equipo (team) de Monday cuyos miembros entran al round-robin
rrEquipo: env('MONDAY_RR_TEAM', 'vendedores'),
inactividadDias: Number(env('INACTIVIDAD_DIAS', '10')),
// Horas que un lead asignado puede seguir en NUEVO/Intento de contacto antes de escalar
escaladoHoras: Number(env('LEAD_ESCALADO_HORAS', '48')),
// User ID de Monday del coordinador que recibe los escalados; vacío = al vendedor asignado
coordinadorUserId: Number(env('MONDAY_COORDINADOR_USER_ID', '0')),
// User ID de Monday de la cuenta COMPARTIDA de vendedores (los vendedores
// de piso operan solo la app, todos ligados a una sola cuenta Monday).
// Cuando un usuario del ecosistema está ligado a esa cuenta, sus listas
// personales de Mi día se filtran por su captura local, NO por la columna
// people (si no, todos verían los leads de todos). 0 = desactivado.
vendedorCompartidoId: Number(env('MONDAY_VENDEDOR_COMPARTIDO_ID', '0')),
pollCron: env('POLL_CRON', '*/5 * * * *'),
recordatorioCron: env('RECORDATORIO_CRON', '0 9 * * *'),
},
recordatoriosCobranza: {
// Escaneo diario de pagos por vencer / vencidos (motor de recordatorios)
cron: env('RECORDATORIOS_CRON', '30 9 * * *'),
// Días de anticipación para el recordatorio "próximo a vencer"
diasAntes: Number(env('RECORDATORIO_DIAS_ANTES', '3')),
// Cada cuántos días se re-recuerda un pago que sigue vencido
revolverDias: Number(env('RECORDATORIO_REVOLVER_DIAS', '7')),
},
ai: {
// Proveedor NAN (OpenAI-compatible): chat + embeddings
apiKey: env('NAN_API_KEY'),
baseUrl: env('NAN_BASE_URL', 'https://api.nan.builders/v1'),
chatModel: env('NAN_CHAT_MODEL', 'qwen3.6'),
embedModel: env('NAN_EMBED_MODEL', 'qwen3-embedding'),
// Umbral mínimo de similitud coseno para recuperar contexto (0-1)
minScore: Number(env('AI_MIN_SCORE', '0.3')),
},
auth: {
jwtSecret: env('JWT_SECRET'),
// Cadena aceptada por jsonwebtoken (p. ej. '7d')
jwtExpiresIn: env('JWT_EXPIRES_IN', '7d'),
// 'false' desactiva la exigencia de token en /api (tests, arranque temprano)
enabled: env('AUTH_ENABLED', 'true') !== 'false',
// Password del usuario admin sembrado (seed-usuarios.ts)
adminPassword: env('ADMIN_PASSWORD', 'Frasa2026'),
},
ligaRemota: {
// Expiración del token de la liga pública de firma/documentos (cadena
// aceptada por jsonwebtoken, p. ej. '72h')
expiresIn: env('LIGA_REMOTA_EXP', '72h'),
},
} as const
export type AppConfig = typeof config