ALICE — Documentación técnica de arquitectura

Versión: 2026-09-18

Objetivo de este documento: describir en profundidad el diseño de ALICE — un asistente conversacional multicanal (voz telefónica, WhatsApp, correo electrónico y chat web) orientado a atención al cliente — de forma que sirva tanto para entender su funcionamiento como para servir de especificación a otro equipo o a un agente de IA que quiera construir una aplicación equivalente desde cero.

Este documento describe patrones de arquitectura y decisiones de diseño, no un despliegue concreto. Se han eliminado deliberadamente nombres de servidores, dominios, direcciones IP, números de teléfono reales, nombres de clientes/organizaciones y cualquier credencial. Donde el original usa un valor real se sustituye por un marcador entre <> o por un nombre genérico (tudominio.com, Organización A, 192.0.2.x, etc.).


1. Visión general

1.1 Qué es

ALICE es un asistente conversacional que atiende en nombre de una persona u organización a través de varios canales de entrada, decide qué hacer con cada mensaje o llamada usando un modelo de lenguaje con *function calling* sobre un conjunto acotado de herramientas, y solo puede actuar dentro de los límites que ese conjunto de herramientas permite (responder, tomar un recado, agendar una cita, transferir una llamada, clasificar como spam...). Nunca ejecuta texto libre ni comandos arbitrarios: toda acción con efecto (crear una cita, transferir, aplicar un cambio de configuración) pasa por una herramienta tipada, validada en el servidor, no solo confiada al modelo.

Es multiempresa (multi-tenant): una misma instalación atiende a varias organizaciones distintas, cada una con sus propios usuarios, números de teléfono/buzones, base de conocimiento, configuración de agenda y datos, completamente aislados entre sí.

1.2 Problema que resuelve

Sustituye a una persona (recepción, contestador, filtro de llamadas comerciales) en la atención de primer nivel de llamadas y mensajes: identificar quién llama y por qué, responder con la información disponible, tomar un recado bien estructurado, filtrar llamadas comerciales no deseadas, gestionar citas, y transferir a una persona humana cuando corresponde — todo con un margen de error muy bajo, porque las decisiones (una transferencia de urgencia, una cita mal anotada, una llamada de spam mal clasificada) tienen coste real.

1.3 Arquitectura de alto nivel


                     ┌──────────────────────────────────────────────┐
                     │                CANALES DE ENTRADA              │
                     │  Voz (PBX) · WhatsApp · Correo · Chat web      │
                     └───────────────────┬────────────────────────────┘
                                         │ normalizan el mensaje entrante
                                         ▼
                     ┌──────────────────────────────────────────────┐
                     │           CAPA DE ENRUTAMIENTO / TENANCY       │
                     │  resuelve: ¿qué organización, qué perfil de    │
                     │  atención, qué configuración aplica?            │
                     └───────────────────┬────────────────────────────┘
                                         ▼
                     ┌──────────────────────────────────────────────┐
                     │              MOTOR CONVERSACIONAL              │
                     │  1. capas deterministas (reglas antes del LLM) │
                     │  2. construcción del prompt de sistema         │
                     │     (identidad + política común + canal +      │
                     │      contexto del contacto + conocimiento)      │
                     │  3. LLM con function calling (tool_choice)     │
                     │  4. validación de la herramienta elegida       │
                     └───────────────────┬────────────────────────────┘
                                         ▼
              ┌─────────────┬────────────┼─────────────┬───────────────┐
              ▼             ▼            ▼             ▼               ▼
         responder    tomar_recado   agenda/citas  transferir     clasificar
                                    (nativa/externa/                 (spam/
                                     BD cliente/API)                 legítima)

Piezas de infraestructura por debajo de estas capas (descritas en detalle en la sección 4):


2. Canales

Cada canal vive como un módulo independiente y autocontenido (presentación, reglas de formato, herramientas disponibles, motor LLM preferido y sus parámetros). Lo que es común a todos los canales (identidad base, políticas de privacidad, método de atención, gestión de la base de conocimiento, máquina de estados de la agenda) vive en una capa compartida, para que mejorar un canal no arrastre efectos secundarios a los demás. Un registro central resuelve qué módulo de canal usar según el tipo de perfil de atención.

2.1 Voz (telefonía)

  1. Responde la llamada.
  2. Crea un *bridge* de mezcla de audio.
  3. Crea un canal de medios externo (ExternalMedia) hacia el servicio de audio, usando un protocolo tipo AudioSocket (TCP, encapsulado, PCM signed-linear a 8 kHz) — un protocolo simple pensado para que un servicio externo reciba y envíe audio en crudo sin tener que hablar SIP/RTP.

Voz sintetizada (TTS): proveedor principal en la nube (voz neuronal, capa gratuita con cuota mensual de caracteres) con conmutación automática a un motor local (Piper, on-device) en tres casos: sin credencial configurada, fallo de la API a mitad de conversación, o cuota mensual agotada. El contador de cuota se guarda en un fichero plano (no en la base de datos) para que la decisión de cambiar de motor no dependa de que la propia base o la API estén disponibles. Solo cuenta lo que realmente se envía al proveedor en la nube; las frases servidas desde caché no consumen cuota.

2.2 WhatsApp

2.3 Correo electrónico

  1. Sobre vacío (MAIL FROM: <>, visible en la cabecera Return-Path con dirección en blanco): es la marca inequívoca de un rebote automático. El RFC 3834 lo prohíbe explícitamente en mayúsculas — nunca se contesta.
  2. Viene de otro buzón propio — el bucle más barato de provocar (dos autorespondedores hablando entre sí) y el más caro de dejar correr.
  3. El propio remitente declara que es automático: cabecera Auto-Submitted distinta de no, Precedence: bulk/list/junk, o X-Auto-Response-Suppress.
  4. Parece un envío masivo aunque no lo declare explícitamente: cabecera Feedback-ID (bucle de reclamaciones de las plataformas de envío masivo), cualquier cabecera List-* del RFC 2369, o un Return-Path con forma de VERP (direcciones de rebote tipo bounces-<muchos dígitos>@...). Esta señal existió porque depender solo de Precedence/List-Id dejó pasar un boletín real que no las llevaba.
  5. Es un informe de entrega/rebote por tipo de contenido (multipart/report, message/delivery-status).
  6. Dirección local por convención "no leída": no-reply, noreply, postmaster, mailer-daemon, newsletter, etc.
  7. No nos han escrito directamente a nosotros: nuestra dirección no aparece en To/Cc ni en Delivered-To/X-Original-To (que es donde el servidor deja la dirección real de entrega tras un reenvío por alias, para no confundir un reenvío legítimo con una copia oculta). Esta señal es la más "blanda": en vez de archivar en silencio como con las anteriores, aquí se aparca el correo con aviso para que lo revise una persona, no se descarta sin más.
  8. Tope diario de respuestas automáticas por remitente: pasado un número máximo (configurable) de respuestas automáticas al mismo remitente el mismo día, se deja de contestar solo — casi siempre señal de un bucle con un sistema automático al otro lado, no de una conversación humana real.

Un correo frenado nunca se pierde: queda guardado en el hilo y marcado para que una persona lo vea; frenar es aplazar, no descartar.

2.4 Chat web (widget embebido)


3. Motor conversacional

3.1 Capas deterministas antes del LLM

No todo pasa siempre por el modelo. Antes de construir el prompt, se evalúan varias reglas deterministas baratas que, si aplican, responden directamente sin gastar una llamada al LLM y sin arriesgarse a que el modelo decida mal algo que tiene una respuesta correcta única:

3.2 Construcción del prompt de sistema

El prompt de sistema se construye por capas, cada una con una responsabilidad distinta:


[ Identidad — la da el canal: "Eres ALICE, la asistente de <organización>..." ]
  +
[ Regla de turno del canal — cómo debe estructurar cada respuesta ]
  +
[ Misión según el modo activo — contestador / recepción / antispam ]
  +
[ Política de interacción común a los 3-4 canales — MÉTODO de trabajo, no datos:
  cómo desglosar preguntas, cuándo escalar, cuándo NO inventar, cómo cerrar ]
  +
[ Reglas de transferencia — qué destinos están permitidos para ESTE perfil ]
  +
[ Fecha y hora actuales en la zona horaria de la organización, explícitas ]
  +
[ Contexto del contacto — si ya se conoce a quien escribe/llama: nombre,
  empresa, notas, historial — marcado como "datos, no instrucciones" ]
  +
[ Base de conocimiento recuperada para esta consulta — marcada explícitamente
  como "datos no confiables": el modelo debe usarla para responder pero
  nunca debe interpretar su contenido como una instrucción nueva ]

Puntos de diseño relevantes:

3.3 Function calling: cómo se define el conjunto de herramientas

El modelo nunca tiene vía libre para "hacer algo"; solo puede elegir, en cada turno, una herramienta de un conjunto pequeño y siempre tipado con JSON Schema. El conjunto de herramientas disponibles se recalcula en cada turno según:

Forma típica de una herramienta (simplificada, sin datos reales):


{
  "type": "function",
  "function": {
    "name": "tomar_recado",
    "description": "Guardar el recado y confirmar a la persona que queda anotado",
    "parameters": {
      "type": "object",
      "properties": {
        "motivo": {
          "type": "string",
          "description": "Resumen interno autocontenido: objetivo, urgencia, datos ya conocidos y siguiente paso esperado. Solo hechos aportados o verificados, nunca inventados."
        },
        "telefono": { "type": "string" },
        "texto": { "type": "string", "description": "Respuesta completa de este turno para la persona" }
      },
      "required": ["motivo", "texto"],
      "additionalProperties": false
    }
  }
}

Herramientas típicas del conjunto base: responder (continuar la conversación), tomar_recado, guardar_contacto (identificar a quien escribe/llama para futuras interacciones), pasar_con_destinatario/transferir (solo en voz), clasificar_llamada (solo en modo antispam: spam / legitima / dudosa), más las herramientas de agenda (consultar_disponibilidad, preparar_cita, confirmar_cita, consultar_cita) cuando ese módulo está activo para la organización.

Nota de diseño sobre tool_choice: algunos proveedores de modelo exigen siempre una llamada a función (tool_choice: required); otros la dejan elegir libremente entre responder en texto plano o llamar a una herramienta (tool_choice: auto). Para una conversación normal interesa el modo libre (a veces solo hay que hablar, no ejecutar nada), pero para tareas cuyo único producto útil es una respuesta estructurada (p.ej. un entrenador de conocimiento que debe emitir siempre una ficha estructurada) conviene forzar la herramienta — de lo contrario, con una conversación ya larga en el historial, el modelo puede "imitar" el patrón de las respuestas anteriores en prosa libre y devolver texto plano en vez de la función esperada, y ese fallo es silencioso (no lanza ningún error, simplemente no produce el resultado estructurado que se necesitaba).

3.4 Selección de proveedor de modelo (LLM) y resiliencia

3.5 Multi-turno y memoria conversacional


4. Infraestructura de voz (telefonía)

4.1 Componentes

4.2 Flujo de una llamada (resumen técnico)

  1. El troncal SIP entrega la llamada al PBX con una cabecera de desvío (Diversion o equivalente) que indica el número original al que se llamó.
  2. El dialplan del contexto de entrada resuelve la organización y el Answer() + entra en Stasis(<app>).
  3. La aplicación recibe el evento de inicio de Stasis, resuelve el perfil de atención exacto (organización + número desviador + número recibido), y crea el bridge + canal de medios externo hacia el servicio de audio.
  4. Bucle por turno: el servicio de audio detecta un segmento de habla (VAD), lo transcribe (STT), lo entrega a la aplicación → la aplicación construye el prompt y llama al LLM → recibe la herramienta elegida y actúa → el texto de respuesta se sintetiza (TTS) y se reproduce.
  5. La llamada termina por colgado de cualquiera de las partes, por silencio prolongado (temporizador de silencio) o por duración máxima (temporizador de seguridad); se cierra el bridge y se libera el canal de medios.

4.3 Registro y trazabilidad de llamadas


5. Guardas de privacidad y anti-abuso

Patrón general en todo el sistema: ante la duda, no se actúa — se pide una aclaración, se aparca con aviso, o se niega explícitamente, nunca se asume. Ejemplos concretos (voz, generalizables a los demás canales):


6. Gestión de secretos y de identidad entre servicios

6.1 Cifrado en reposo de credenciales guardadas en base de datos

Los secretos que hay que guardar y poder recuperar (credenciales de integraciones de agenda de cada cliente, tokens OAuth de calendario externo) se cifran con AES-256-GCM (cifrado autenticado: un valor manipulado falla al descifrar en vez de devolver datos corruptos que podrían acabar usándose igualmente). La clave de cifrado se deriva con SHA-256 a partir de un secreto de sesión de la aplicación combinado con una etiqueta fija por caso de uso (p. ej. una etiqueta para credenciales de agenda, otra distinta para tokens OAuth de calendario). Consecuencias de diseño a tener en cuenta si se replica este patrón:

6.2 Secretos que solo se leen de fichero, nunca de base de datos ni de variables de entorno en texto plano dentro del repositorio

Ciertas credenciales especialmente sensibles (tokens de administración de una API de mensajería, claves de un vault centralizado) se leen de un fichero con permisos restringidos (0600, solo el usuario del proceso) en vez de guardarse en base de datos o en el propio .env versionado — permite rotarlas sin tocar despliegue ni base de datos, y limita su exposición a backups o exports de la base de datos.

6.3 Patrón de vault centralizado con identidad mTLS (opcional pero recomendable)

Para las llamadas a proveedores de IA externos (LLM, transcripción), el patrón más seguro observado es no distribuir la clave del proveedor a cada servidor que la necesita, sino:

  1. Un servicio de vault centralizado (un único punto, en un servidor dedicado) guarda las claves reales de los proveedores externos.
  2. Cada servidor cliente autorizado tiene su propia identidad mTLS (certificado + clave privada propios, más el certificado de la CA del vault, más un token de autenticación de aplicación) — nunca comparte la clave del proveedor externo, solo puede pedirle al vault que haga la llamada en su nombre.
  3. El cliente llama al vault por HTTPS con TLSv1.3 mínimo y el par certificado/clave como autenticación de transporte, más un token de aplicación en la cabecera Authorization.
  4. El vault registra cada uso (qué cliente, qué tarea, cuánto — p. ej. milisegundos de audio transcritos) para poder atribuir costes y detectar anomalías, y es el único punto donde vive la clave real del proveedor externo.
  5. Si el vault no está disponible o no está configurado, el cliente cae a un modo alternativo (usar sus propias claves de proveedor configuradas localmente, si las tiene) en vez de bloquear el servicio entero.

Esto reduce drásticamente la superficie de exposición de las claves más caras/sensibles: solo un servidor las conoce, y el resto de la flota solo tiene certificados de cliente revocables individualmente.

6.4 Notas para variables de configuración sensibles


7. Persistencia y modelo de datos

7.1 Motor

Base de datos relacional embebida (SQLite) en modo WAL (Write-Ahead Logging), que permite lecturas concurrentes mientras se escribe. Decisión de diseño importante: una base SQLite en WAL no debe montarse sobre un sistema de ficheros de red — el bloqueo del WAL puede comportarse mal o directamente corromper el estado bajo NFS/CIFS. El patrón recomendado es mantener la base "viva" (la que atiende tráfico en tiempo real) en disco local rápido, y replicar snapshots consistentes periódicamente a otro nodo para backup/archivo — nunca al revés.

7.2 Entidades principales (agrupadas por dominio)

Multi-tenancy y usuarios

Llamadas

Enrutamiento de entrada

Canales de texto

Contactos y CRM ligero

Base de conocimiento (ver §8 para el pipeline completo)

Agenda / reservas (ver §9)

Facturación por uso

Auditoría

7.3 Principio de aislamiento por organización

Prácticamente toda tabla con datos de negocio lleva tenant_id, y toda consulta de la API filtra siempre por el tenant_id de la sesión activa — nunca por un parámetro que llegue del cliente sin validar contra la sesión. El alcance (a qué organización(es) puede acceder una sesión) lo decide siempre el servidor a partir del usuario autenticado, nunca un parámetro de la URL: es la única barrera real entre los datos de una organización y los de otra. Un rol de plataforma puede cambiar de organización activa explícitamente; un rol normal de organización queda atado a la suya.


8. Base de conocimiento (RAG)

8.1 Ciclo de vida: entrenamiento conversacional → aprobación → publicación

8.2 Recuperación híbrida (retrieval)

Pipeline de recuperación para una consulta dada (resumen del patrón observado, generalizable):

  1. Embeddings: se calcula un embedding de la consulta con un proveedor externo de embeddings si el proveedor está configurado y disponible; si no, se cae a un vector léxico local simple (hash de tokens) como degradación funcional en vez de fallo total.
  2. Puntuación combinada: cada fragmento candidato se puntúa como una mezcla ponderada de similitud vectorial (coseno, peso dominante) y coincidencia léxica simple (peso menor) — la combinación es más robusta frente a fallos puntuales del proveedor de embeddings que depender de uno solo.
  3. Preselección + rerank: se toman los N mejores candidatos por la puntuación combinada (p. ej. 12) y se pasan por un paso adicional de reordenación (rerank, opcionalmente con un modelo dedicado a esa tarea) antes de quedarse con el top final (p. ej. 5-8).
  4. Recorte por presupuesto de caracteres: los fragmentos elegidos se recortan para no superar un tope de caracteres total en el contexto del prompt — mejor un fragmento incompleto que quedarse sin espacio para el resto de las capas del prompt.
  5. Auditoría de cada recuperación: se registra la consulta, qué fragmentos se usaron y la latencia — permite depurar por qué el asistente respondió (o no respondió) algo concreto en una conversación real.
  6. Todo el contexto recuperado se marca explícitamente en el prompt como datos no confiables: instrucción explícita al modelo de usarlo solo para responder, nunca como una instrucción nueva de comportamiento, y de admitir honestamente cuando la base no tiene una respuesta suficiente en vez de inventar.

8.3 Aislamiento de la base de conocimiento

Todo acceso a la base de conocimiento se filtra por organización, perfil de atención y versión — igual que el resto de los datos de negocio (ver §7.3). Un perfil puede además estar configurado para heredar el conocimiento de otro perfil/nivel superior (p. ej. una configuración compartida de organización) en vez de tener su propia base, según el modelo de "alcance" que se defina.

8.4 Conectores dinámicos de solo lectura contra sistemas del cliente

Además del contenido "subido" (documentos, fichas de entrenamiento), el sistema soporta conectores de solo lectura contra bases de datos operativas del propio cliente (motores relacionales habituales tipo MySQL/MariaDB, SQL Server, y también MongoDB; opcionalmente vía túnel SSH si la base no es alcanzable directamente), para poder responder preguntas sobre datos que cambian constantemente (p. ej. estado de un pedido) sin tener que reentrenar el conocimiento estático. Puntos de diseño:


9. Agenda y reservas

Se soportan varios modos de gestión de agenda por organización, pensados para cubrir desde "no tenemos nada, que lo lleve el propio asistente" hasta "ya tenemos un sistema y solo queremos que respete lo que hay":

9.1 Agenda nativa

El propio sistema es el motor de citas: catálogo de servicios (con duración), profesionales/agentes, horario de apertura, cierres/festivos, y las citas en sí. El asistente calcula huecos disponibles cruzando servicio + profesional + horario + citas ya existentes + cierres, y puede crear, consultar, mover y cancelar citas directamente. Detalle importante de diseño: mover una cita existente es una operación de actualización (UPDATE) sobre la fila existente, nunca "cancelar la vieja y crear una nueva" — lo segundo pierde el historial y puede dejar un hueco cancelado que se vuelve a ofrecer por error a otra persona a la vez que se crea la nueva.

9.2 Agenda externa (calendario personal por profesional)

Cada profesional conecta su propio calendario personal (p. ej. vía OAuth a un proveedor de calendario en la nube) y el asistente respeta lo que ya tiene ocupado. Punto de diseño clave, poco intuitivo a primera vista: esto no sustituye al motor nativo, lo complementa. Un calendario personal solo sabe decir "libre/ocupado" en una franja — no conoce los servicios que ofrece el negocio, sus duraciones, ni el horario laboral general. Así que el cálculo de huecos lo sigue haciendo el motor nativo con los datos propios del sistema (servicios, duraciones, horario), y del calendario externo solo se obtiene una lista de franjas ocupadas que se resta exactamente igual que un cierre manual. Construir un segundo motor de citas paralelo duplicaría toda la lógica anti-solape y el tratamiento de zonas horarias — se evita a propósito.

Arquitectura del conector OAuth (aplicable a cualquier proveedor de calendario compatible):

9.3 Agenda en la base de datos propia del cliente

Variante de "conector dinámico" (ver §8.4) pero con capacidad de escritura en vez de solo lectura, para clientes que ya tienen su propio sistema de gestión con base de datos accesible.

9.4 API de reservas externa (protocolo propio)

Para integraciones más avanzadas, un protocolo HTTP con una URL base y un secreto por organización (cifrado igual que el resto). Las propuestas de reserva generadas en una conversación se guardan con un identificador idempotente y un tiempo de expiración corto — la escritura contra el sistema externo solo se dispara tras una confirmación explícita de la persona, nunca de forma especulativa, y el identificador idempotente evita duplicar la reserva si hay un reintento de red.

9.5 Guardas comunes a los cuatro modos


10. Notificaciones


11. Esquema de configuración (variables de entorno)

Agrupadas por área. Se muestran solo los nombres y su propósito, nunca valores reales.


# --- Núcleo del servicio ---
PORT=                          # puerto HTTP del proceso de aplicación
PUBLIC_BASE_URL=                # URL pública base (detrás del proxy inverso)
DATA_DIR=                       # raíz de datos locales (BD, caché, ficheros temporales)
SESSION_SECRET=                 # secreto de firma de sesión Y de derivación de claves de cifrado (§6.1) — alta entropía, no rotar sin plan de migración
ADMIN_USER=
ADMIN_PASSWORD=

# --- Telefonía (ARI / PBX) ---
ARI_URL=                        # solo alcanzable en red interna, nunca expuesto a Internet
ARI_USER=
ARI_PASSWORD=
ARI_APP=                        # nombre de la aplicación Stasis
AUDIOSOCKET_HOST=                # host:puerto donde escucha el servicio de audio
MEDIA_BUS_TOKEN=                 # token interno compartido entre la app y el servicio de audio
NATIVE_CALL_TOKEN=               # token para despertar la app móvil nativa en una llamada entrante
TRANSFER_ENDPOINT=               # destino PJSIP de recuperación humana por defecto
ASTERISK_SSH_HOST=                # para lectura de logs/gestión remota, con lista blanca de fuentes
SIP_DOMAIN=
SIP_EXTENSION_START= / SIP_EXTENSION_END=   # rango de extensiones de operador

# --- Proveedores de modelo (LLM) ---
DEEPSEEK_API_KEY= / DEEPSEEK_BASE_URL= / DEEPSEEK_MODEL=
NVIDIA_API_KEY= / NVIDIA_BASE_URL= / NVIDIA_DEFAULT_MODEL= / NVIDIA_ASSISTANT_MODEL=
ASSISTANT_LLM_TIMEOUT_MS= / LLM_TIMEOUT_MS=
NVIDIA_EMBEDDING_MODEL= / NVIDIA_EMBEDDING_DIMENSIONS= / NVIDIA_RERANK_MODEL=

# --- Vault centralizado / identidad mTLS (opcional, ver §6.3) ---
APIGATE_BASE_URL=
APIGATE_TOKEN_FILE= / APIGATE_CA_FILE= / APIGATE_CERT_FILE= / APIGATE_KEY_FILE=

# --- Voz: reconocimiento (STT) ---
NVIDIA_ASR_MODE= / NVIDIA_ASR_LANGUAGE= / NVIDIA_ASR_TIMEOUT=
WHISPER_MODEL= / WHISPER_THREADS= / WHISPER_BEAM_SIZE=   # motor STT local de respaldo/validación
VAD_MIN_SILENCE_MS= / VAD_SPEECH_PAD_MS=
STT_MIN_LOCAL_CONFIDENCE=
STT_EVAL_ENABLED= / STT_EVAL_MAX_SAMPLES=      # captura de muestras reales para medir error de transcripción

# --- Voz: síntesis (TTS) ---
TTS_VOICE=
AZURE_SPEECH_KEY= / AZURE_SPEECH_REGION=        # proveedor TTS en la nube (o el que se use)
AZURE_VOICE_STYLE= / AZURE_VOICE_RATE= / AZURE_VOICE_PITCH=
AZURE_TTS_TARGET_RMS= / AZURE_TTS_CONTRAST=
AZURE_TTS_MONTHLY_LIMIT= / AZURE_TTS_SAFETY_MARGIN=     # cuota mensual y margen antes de caer al motor local

# --- WhatsApp ---
WHATSAPP_RESOLVE_TOKEN=          # token propio para que la pasarela pregunte de quién es un número
WHATSAPP_ADMIN_TOKEN_FILE= / WHATSAPP_PIN_FILE=   # credenciales de alta de número, en fichero, no en .env
WHATSAPP_WABA_ID= / WHATSAPP_GRAPH_VERSION= / WHATSAPP_COUNTRY_CODE=
WHATSAPP_GATEWAY_HEALTH_URL= / WHATSAPP_GATEWAY_SEND_URL= / WHATSAPP_GATEWAY_API_KEY=

# --- Correo ---
EMAIL_POLL_INTERVAL_MS=
EMAIL_MAX_BODY_CHARS= / EMAIL_MAX_ATTACHMENT_BYTES=
EMAIL_AUTOREPLY_DAILY_CAP=        # tope de respuestas automáticas por remitente/día (§2.3)
EMAIL_DEFAULT_IMAP_HOST= / EMAIL_DEFAULT_SMTP_HOST=

# --- Base de conocimiento ---
KNOWLEDGE_MAX_FILE_BYTES= / KNOWLEDGE_MAX_CONTEXT_CHARS= / KNOWLEDGE_RETRIEVAL_TIMEOUT_MS=
KNOWLEDGE_WEB_MAX_PAGES= / KNOWLEDGE_WEB_DEFAULT_PAGES= / KNOWLEDGE_WEB_PAGE_BYTES=
KNOWLEDGE_WEB_TOTAL_CHARS= / KNOWLEDGE_WEB_PAGE_TIMEOUT_MS= / KNOWLEDGE_WEB_TOTAL_TIMEOUT_MS=
KNOWLEDGE_WEB_ALLOWED_PRIVATE_HOSTS=   # excepciones EXACTAS a la protección SSRF del rastreador, sin comodines
KNOWLEDGE_CONNECTOR_TIMEOUT_MS= / KNOWLEDGE_CONNECTOR_MAX_ROWS= / KNOWLEDGE_CONNECTOR_MAX_ENTITIES=
KNOWLEDGE_SSH_PRIVATE_KEY= / KNOWLEDGE_SSH_MAX_OUTPUT_BYTES=

# --- Agenda / calendario externo ---
GOOGLE_OAUTH_CLIENT_ID= / GOOGLE_OAUTH_CLIENT_SECRET= / GOOGLE_OAUTH_SCOPES=
GOOGLE_DATA_ISOLATION=            # on = nunca reenviar contenido del calendario externo al LLM (§3.4, §9.2)
GOOGLE_LOGIN_ENABLED= / GOOGLE_LOGIN_CLIENT_ID=   # login social independiente del consentimiento de calendario

# --- Planificador de flujos asistido por IA (herramienta de administración) ---
ALICETECH_PLANNER_MODE=            # off | shadow | on — despliegue por fases (§12)
PLANNER_LLM_TIMEOUT_MS= / PLANNER_BUDGET_MS=   # el segundo debe quedar por debajo del timeout del proxy

# --- Límites operativos y de llamada ---
MAX_CALL_SECONDS= / SILENCE_HANGUP_SECONDS=

# --- Notificaciones ---
TELEGRAM_NOTIFY_SCRIPT=
WHATSAPP_TELEGRAM_ROUTES=          # pares organización:número en supervisión, opt-in

# --- Alta de prueba / autoservicio (si existe landing pública) ---
DEFAULT_OUTBOUND_DDI=
TRIAL_SIGNUP_ENABLED= / TRIAL_TENANT_SLUG= / TRIAL_AI_CREDIT_CENTIMOS= / TRIAL_AI_TARIFF_CENTIMOS_MILLION=
AI_TARIFF_INPUT_CENTIMOS_MILLION= / AI_TARIFF_OUTPUT_CENTIMOS_MILLION=
TRIAL_MAX_SIGNUPS_PER_IP_DAY= / TRIAL_SIGNUP_NOTIFICATION_EMAIL=
DEMO_REQUEST_EMAIL= / DEMO_MAX_REQUESTS_IP_DAY=

# --- Mensaje legal ---
LEGAL_GREETING=                    # aviso legal de grabación, obligatorio en la mayoría de jurisdicciones

12. Portales de administración y operación

Tres portales separados, sobre la misma aplicación, cada uno con un alcance de permisos distinto:

  1. Portal de agente/operación: llamadas en vivo (audio, transcripción, intervención humana), historial, contactos, softphone WebRTC individual, y una sección de autoservicio ("mi configuración") donde un agente puede ajustar el comportamiento y el conocimiento de solo las rutas que tiene asignadas.
  2. Portal de administración de organización: organizaciones, usuarios y roles, rutas de recepción, cuentas CRM, contactos, auditoría — alcance limitado a la(s) organización(es) del usuario.
  3. Portal de centralita (PBX): administración de la infraestructura de telefonía en sí (extensiones SIP, estado de registro de dispositivos, CDR, dialplan, topología visual de la configuración VoIP, logs del PBX y del edge SIP filtrados por identificador de llamada) — separado del portal de organización porque administra la máquina, no los datos de un cliente. Solo accesible a roles de plataforma (y opcionalmente un rol delegado de "solo mantenimiento de PBX" que no da control sobre el resto de la plataforma).

12.1 Copiloto de configuración asistido por IA (patrón "Alicetech")

Un patrón interesante y reutilizable: un asistente de IA integrado en el propio portal de administración técnica, con reglas estrictas para evitar que un error del modelo pueda romper la configuración en producción:


13. Testing


14. Decisiones de diseño no obvias (resumen para quien reconstruya el sistema)

Esta sección reúne, en un solo sitio, los aprendizajes de arquitectura más fáciles de pasar por alto si se reconstruye el sistema desde cero:

  1. Separar por canal, no por "modo de texto vs. voz". Cada canal tiene su propia identidad, reglas de formato, conjunto de herramientas y motor preferido, como módulos independientes con la misma forma — cambiar el canal de voz no debe poder romper WhatsApp, y viceversa. Lo común de verdad (identidad base, política de interacción, privacidad, conocimiento) vive en una capa compartida separada, no duplicada en cada canal.
  2. Ninguna acción con efecto real se confía solo al modelo. El modelo elige una herramienta tipada; el servidor vuelve a validar contra la configuración real (listas de destinos, existencia de un servicio/profesional, límites) antes de ejecutar cualquier cosa irreversible.
  3. Todo contenido no confiable (contexto de contacto, conocimiento recuperado, correo citado) se marca explícitamente como datos, nunca como instrucciones, en el propio texto del prompt. Es la defensa contra inyección de prompt más simple y más barata de aplicar, y hay que aplicarla en cada punto donde entra contenido que no ha escrito el operador del sistema.
  4. La fecha/hora actual se inyecta explícitamente en cada prompt. Un modelo nunca debe usar su fecha de "hoy" interna para resolver expresiones relativas.
  5. Multi-proveedor de LLM con caída automática, decidido por medición, no por preferencia técnica abstracta. Guardar el resultado de la comparación (qué proveedor, con qué latencia, en qué caso) como comentario junto a la configuración evita que alguien "optimice" el orden sin conocer el motivo real.
  6. Un modelo con razonamiento interno puede devolver una respuesta vacía sin error HTTP si su presupuesto de tokens se agota pensando. Hay que detectarlo explícitamente (ni texto ni llamada a función) y reintentar, no asumir que un 200 OK siempre trae algo útil.
  7. El primer puntero de lectura de un buzón/cola con historial nunca debe ser "desde el principio". Y el puntero se avanza antes de procesar el lote, no después, para que un fallo a mitad de proceso repita un correo sin respuesta en vez de duplicar una respuesta ya enviada.
  8. Un rango de consulta "desde N en adelante" en IMAP (y en protocolos similares) puede devolver un resultado "fantasma" aunque no haya nada nuevo; hay que descartar explícitamente lo que ya se procesó por identificador, no confiar en que el filtro del propio protocolo baste.
  9. La confirmación de una acción reversible por voz y por escrito no debe ser la misma lógica. El reconocimiento de voz puede fallar de formas que el texto escrito no puede; una cita creada de más por una mala transcripción tiene coste real. Confirmar en dos pasos por voz y en un paso por escrito es una decisión de diseño, no una inconsistencia a limpiar.
  10. Un calendario externo conectado por el cliente no sustituye al motor de reservas propio — lo complementa con una única señal (ocupado/libre). Construir un segundo motor de citas duplicaría toda la lógica de anti-solape.
  11. Multi-tenancy real significa que el servidor decide el alcance, nunca un parámetro que llega del cliente. Cualquier endpoint que acepte un identificador de organización en la petición debe ignorarlo para roles no globales y usar siempre el de la sesión.
  12. Cifrar en reposo con una etiqueta fija por caso de uso, nunca compartida entre módulos distintos. Limita el radio de un secreto de almacén comprometido y permite rotar por módulo.
  13. Un asistente de IA con permiso de tocar configuración de producción necesita un vocabulario cerrado (esquema tipado), no texto libre, y un ciclo obligatorio de simulación + revisión humana antes de aplicar. Y su despliegue debe empezar en modo "solo observar y comparar" antes de tener efecto real.
  14. Todo aviso/notificación generado durante pruebas debe llevar una marca inconfundible. Un falso positivo indistinguible de un incidente real cuesta más caro que no probar en absoluto: quema la credibilidad del sistema de avisos exactamente el día que hace falta que alguien confíe en él.
  15. Backups y réplicas de una base de datos "viva" en modo WAL van a otro nodo por copia periódica de snapshot consistente, nunca por montaje de red de la base activa.

15. Stack tecnológico (resumen)

ComponenteTecnología
Proceso de aplicación (API + orquestación)Node.js (≥20), sin framework web pesado — servidor HTTP nativo con enrutamiento propio
Base de datosSQLite embebida, modo WAL
Cliente de telefoníaCliente ARI (REST + WebSocket) contra Asterisk
Protocolo de audio en tiempo realAudioSocket (TCP, PCM signed-linear 8 kHz)
Servicio de procesamiento de audioPython 3, en contenedor separado
VADSilero VAD
STT (reconocimiento de voz)Motor tipo Whisper (faster-whisper) local, con opción de un proveedor en la nube vía el vault centralizado (§6.3) y validación cruzada entre ambos
TTS (síntesis de voz)Proveedor neuronal en la nube (voz principal) + motor local tipo Piper (respaldo automático)
Softphone de agenteSIP sobre WebSocket + WebRTC (librería cliente SIP.js)
LLM conversacionalProveedores compatibles con Chat Completions + function calling (p. ej. un proveedor rápido de baja latencia para conversación en directo, y un proveedor de modelos grandes para tareas de fondo más exigentes)
Embeddings / rerankProveedor de IA con endpoints de embeddings y de reordenación (rerank) dedicados
Frontend (PWA)React (v19) + Vite, Service Worker (PWA instalable)
Envoltorio móvilCapacitor (Android), con plugins nativos para llamadas entrantes en segundo plano y compartir/guardar ficheros
Cifrado de secretos en reposoAES-256-GCM (nativo del runtime, sin librería externa)
Autenticación de sesiónCookie firmada con HMAC-SHA256, HttpOnly + SameSite=Strict
NotificacionesWeb Push (VAPID) + un bot de mensajería externo para avisos operativos
Conectores de datos externosClientes nativos para motores relacionales habituales y para una base de datos documental, más SSH para acceso remoto cuando hace falta
Proceso de despliegueGestor de procesos del sistema operativo (unidades de servicio) + proxy inverso HTTP con terminación TLS delante de todos los componentes

*Fin del documento.*