Bot: al cerrar la cualificación, Luisa pide una foto y entra en modo recogida; al llegar la foto la sube como "antes" con perfilCompleto:true → dispara render + presupuesto + entrega del PDF. Nuevo webhook /whatsapp-fotos para que, tras una llamada, Luisa escriba al lead, referencie lo hablado y le pida las fotos (reutiliza el mismo modo). App: el webhook de Retell, tras el análisis de la llamada, llama a pedirFotosWhatsapp (WHATSAPP_FOTOS_WEBHOOK_URL) con el contexto de la reforma. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
// Variables de entorno de integraciones externas, validadas con zod (lo exige CLAUDE.md).
|
|
// Son OPCIONALES a propósito: sin ellas el funnel sigue en modo simulado, y ni el build ni
|
|
// la demo dependen de tener claves cargadas.
|
|
const opcional = z.preprocess(
|
|
(v) => (v === '' || v === undefined ? undefined : v),
|
|
z.string().min(1).optional(),
|
|
);
|
|
|
|
const schema = z.object({
|
|
RETELL_API_KEY: opcional,
|
|
RETELL_AGENT_ID: opcional,
|
|
RETELL_FROM_NUMBER: opcional,
|
|
// Allowlist de pruebas: si tiene valor (CSV de números), SOLO se llaman esos; vacío = todos.
|
|
RETELL_ALLOWED_NUMBERS: 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,
|
|
// Cross-canal: tras una llamada, pedir al lead las fotos por WhatsApp.
|
|
WHATSAPP_FOTOS_WEBHOOK_URL: opcional,
|
|
// Base pública de la app, para construir enlaces (ej. el enlace al formulario en el email).
|
|
APP_URL: opcional,
|
|
// LLM (OpenRouter) para el post-análisis de la conversación de WhatsApp.
|
|
OPENROUTER_API_KEY: opcional,
|
|
OPENROUTER_MODEL_ANALISIS: opcional,
|
|
});
|
|
|
|
export const env = schema.parse({
|
|
RETELL_API_KEY: process.env.RETELL_API_KEY,
|
|
RETELL_AGENT_ID: process.env.RETELL_AGENT_ID,
|
|
RETELL_FROM_NUMBER: process.env.RETELL_FROM_NUMBER,
|
|
RETELL_ALLOWED_NUMBERS: process.env.RETELL_ALLOWED_NUMBERS,
|
|
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,
|
|
WHATSAPP_FOTOS_WEBHOOK_URL: process.env.WHATSAPP_FOTOS_WEBHOOK_URL,
|
|
APP_URL: process.env.APP_URL,
|
|
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,
|
|
OPENROUTER_MODEL_ANALISIS: process.env.OPENROUTER_MODEL_ANALISIS,
|
|
});
|
|
|
|
// Mínimo para lanzar una llamada saliente: clave de API + número de origen. El agente puede
|
|
// ir ligado al número en el panel; si además se define RETELL_AGENT_ID, se manda como
|
|
// override_agent_id en cada llamada.
|
|
export function retellConfigurado(): boolean {
|
|
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);
|
|
}
|
|
|
|
export function whatsappFotosConfigurado(): boolean {
|
|
return Boolean(env.WHATSAPP_FOTOS_WEBHOOK_URL);
|
|
}
|