Files
reformix-hackaton/mvp/b2c/src/lib/env.ts
Carlos Narro b0871b733c Paso de fotos + flujo cross-canal llamada→WhatsApp para el render
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>
2026-06-10 12:50:33 +02:00

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);
}