Añade env de ingesta, SMTP y webhooks + dependencia nodemailer
Variables nuevas (todas opcionales vía zod, sin romper build/demo): - FUNNEL_API_KEY: clave Bearer del EP de ingesta. - SMTP_* + EMAIL_FROM: envío de email del presupuesto/enlace. - PERFIL/WHATSAPP/WHATSAPP_START webhook URLs: señales al flujo externo. - APP_URL: base para enlaces absolutos. Helpers emailConfigurado()/perfilWebhookConfigurado()/whatsappWebhookConfigurado()/ whatsappStartConfigurado(). nodemailer como dep directa (stack: Email SMTP). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,3 +7,26 @@ DATABASE_URL="postgresql://postgres:reformix@localhost:5432/reformix"
|
|||||||
RETELL_API_KEY=""
|
RETELL_API_KEY=""
|
||||||
RETELL_AGENT_ID=""
|
RETELL_AGENT_ID=""
|
||||||
RETELL_FROM_NUMBER="" # número de origen en E.164, p. ej. +34910000000
|
RETELL_FROM_NUMBER="" # número de origen en E.164, p. ej. +34910000000
|
||||||
|
|
||||||
|
# EP de ingesta del lead (/api/leads/:id/ingesta). Clave compartida que valida al llamante
|
||||||
|
# externo (Authorization: Bearer ...). Sin ella, el EP responde 401.
|
||||||
|
FUNNEL_API_KEY=""
|
||||||
|
|
||||||
|
# Email (SMTP) para enviar el presupuesto y el enlace al formulario. OPCIONALES: sin SMTP_HOST +
|
||||||
|
# EMAIL_FROM el envío degrada a no-op (la entrega queda marcada como simulada). Mailhog local:
|
||||||
|
# SMTP_HOST=localhost SMTP_PORT=1025 (sin user/pass).
|
||||||
|
SMTP_HOST=""
|
||||||
|
SMTP_PORT="587"
|
||||||
|
SMTP_USER=""
|
||||||
|
SMTP_PASS=""
|
||||||
|
EMAIL_FROM="" # remitente, p. ej. "Reformas Ejemplo <no-reply@reformix.es>"
|
||||||
|
|
||||||
|
# Webhooks salientes hacia el flujo externo (n8n/generador). OPCIONALES: sin URL la señal no se
|
||||||
|
# manda. PERFIL = perfil completo (generar renders/agente); WHATSAPP = entrega del PDF;
|
||||||
|
# WHATSAPP_START = arrancar la conversación de WhatsApp con el lead.
|
||||||
|
PERFIL_WEBHOOK_URL=""
|
||||||
|
WHATSAPP_WEBHOOK_URL=""
|
||||||
|
WHATSAPP_START_WEBHOOK_URL=""
|
||||||
|
|
||||||
|
# Base pública de la app, para construir enlaces absolutos (enlace al formulario en el email).
|
||||||
|
APP_URL="http://localhost:3000"
|
||||||
|
|||||||
21
mvp/b2c/package-lock.json
generated
21
mvp/b2c/package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "^0.45.2",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
|
"nodemailer": "^8.0.10",
|
||||||
"postcss": "^8.5.15",
|
"postcss": "^8.5.15",
|
||||||
"postgres": "^3.4.9",
|
"postgres": "^3.4.9",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/nodemailer": "^8.0.0",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@vitest/coverage-v8": "^4.1.7",
|
"@vitest/coverage-v8": "^4.1.7",
|
||||||
@@ -2966,6 +2968,16 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/nodemailer": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.15",
|
"version": "19.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
|
||||||
@@ -7201,6 +7213,15 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemailer": {
|
||||||
|
"version": "8.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.10.tgz",
|
||||||
|
"integrity": "sha512-BLFuSth7QtHOkBzyqTehWWyub0NTRDuK2Q2SQfnGLsrJnzyU+Yeh4WpV1eZGuARFj1xQJHIdnTuJZLP+b9R1GQ==",
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-svg-path": {
|
"node_modules/normalize-svg-path": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "^0.45.2",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
|
"nodemailer": "^8.0.10",
|
||||||
"postcss": "^8.5.15",
|
"postcss": "^8.5.15",
|
||||||
"postgres": "^3.4.9",
|
"postgres": "^3.4.9",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/nodemailer": "^8.0.0",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@vitest/coverage-v8": "^4.1.7",
|
"@vitest/coverage-v8": "^4.1.7",
|
||||||
|
|||||||
@@ -12,12 +12,36 @@ const schema = z.object({
|
|||||||
RETELL_API_KEY: opcional,
|
RETELL_API_KEY: opcional,
|
||||||
RETELL_AGENT_ID: opcional,
|
RETELL_AGENT_ID: opcional,
|
||||||
RETELL_FROM_NUMBER: opcional,
|
RETELL_FROM_NUMBER: opcional,
|
||||||
|
// EP de ingesta del lead: clave compartida que valida al llamante externo.
|
||||||
|
FUNNEL_API_KEY: opcional,
|
||||||
|
// SMTP para enviar el presupuesto y el enlace al formulario.
|
||||||
|
SMTP_HOST: opcional,
|
||||||
|
SMTP_PORT: opcional,
|
||||||
|
SMTP_USER: opcional,
|
||||||
|
SMTP_PASS: opcional,
|
||||||
|
EMAIL_FROM: opcional,
|
||||||
|
// Webhooks salientes hacia el flujo externo (generación, entrega y arranque de WhatsApp).
|
||||||
|
PERFIL_WEBHOOK_URL: opcional,
|
||||||
|
WHATSAPP_WEBHOOK_URL: opcional,
|
||||||
|
WHATSAPP_START_WEBHOOK_URL: opcional,
|
||||||
|
// Base pública de la app, para construir enlaces (ej. el enlace al formulario en el email).
|
||||||
|
APP_URL: opcional,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const env = schema.parse({
|
export const env = schema.parse({
|
||||||
RETELL_API_KEY: process.env.RETELL_API_KEY,
|
RETELL_API_KEY: process.env.RETELL_API_KEY,
|
||||||
RETELL_AGENT_ID: process.env.RETELL_AGENT_ID,
|
RETELL_AGENT_ID: process.env.RETELL_AGENT_ID,
|
||||||
RETELL_FROM_NUMBER: process.env.RETELL_FROM_NUMBER,
|
RETELL_FROM_NUMBER: process.env.RETELL_FROM_NUMBER,
|
||||||
|
FUNNEL_API_KEY: process.env.FUNNEL_API_KEY,
|
||||||
|
SMTP_HOST: process.env.SMTP_HOST,
|
||||||
|
SMTP_PORT: process.env.SMTP_PORT,
|
||||||
|
SMTP_USER: process.env.SMTP_USER,
|
||||||
|
SMTP_PASS: process.env.SMTP_PASS,
|
||||||
|
EMAIL_FROM: process.env.EMAIL_FROM,
|
||||||
|
PERFIL_WEBHOOK_URL: process.env.PERFIL_WEBHOOK_URL,
|
||||||
|
WHATSAPP_WEBHOOK_URL: process.env.WHATSAPP_WEBHOOK_URL,
|
||||||
|
WHATSAPP_START_WEBHOOK_URL: process.env.WHATSAPP_START_WEBHOOK_URL,
|
||||||
|
APP_URL: process.env.APP_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mínimo para lanzar una llamada saliente: clave de API + número de origen. El agente puede
|
// Mínimo para lanzar una llamada saliente: clave de API + número de origen. El agente puede
|
||||||
@@ -26,3 +50,21 @@ export const env = schema.parse({
|
|||||||
export function retellConfigurado(): boolean {
|
export function retellConfigurado(): boolean {
|
||||||
return Boolean(env.RETELL_API_KEY && env.RETELL_FROM_NUMBER);
|
return Boolean(env.RETELL_API_KEY && env.RETELL_FROM_NUMBER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mínimo para enviar email: host SMTP + remitente. Sin esto el envío degrada a no-op.
|
||||||
|
export function emailConfigurado(): boolean {
|
||||||
|
return Boolean(env.SMTP_HOST && env.EMAIL_FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cada webhook saliente es opcional: si falta su URL, la señal correspondiente no se manda.
|
||||||
|
export function perfilWebhookConfigurado(): boolean {
|
||||||
|
return Boolean(env.PERFIL_WEBHOOK_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function whatsappWebhookConfigurado(): boolean {
|
||||||
|
return Boolean(env.WHATSAPP_WEBHOOK_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function whatsappStartConfigurado(): boolean {
|
||||||
|
return Boolean(env.WHATSAPP_START_WEBHOOK_URL);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user