Entorno local
Requisitos previos
- Node.js 20+ y pnpm (no usar npm/yarn en ningún repo de Aura).
- Docker (para PostgreSQL + pgvector).
- Ajolote corriendo (servicio de auth) — o acceso al ajolote de dev.
- Token de GitHub con scope
read:packagespara instalar los paquetes privados@cuatro-quinas/*.
GitHub Packages (una sola vez)
Los paquetes @cuatro-quinas/ajolote-* se publican en GitHub Packages. Configurá tu
~/.npmrc de usuario (no el del repo):
//npm.pkg.github.com/:_authToken=ghp_TU_TOKEN
Alternativa por sesión:
export GITHUB_TOKEN=ghp_...antes depnpm install.
Backend (Aura-reloaded)
pnpm install
cp env.example .env # completar DATABASE_URL, OPENAI_API_KEY, etc.
docker compose up -d postgres # PostgreSQL + pgvector
docker compose up -d rabbitmq # (opcional) cola de /anfibios/transcriptions — UI en :15672
pnpm db:push # aplica el schema (solo dev)
pnpm ingest:manuals # (opcional) carga manuales en knowledge_base
pnpm start:dev # → API en http://localhost:3000
El módulo Transcripciones (
/anfibios/transcriptions) es solo local: necesita RabbitMQ (eldocker compose up -d rabbitmqde arriba) +RABBITMQ_URL. Sin eso, subir audios responde 503; el resto de Aura anda igual.
Variables de entorno (backend)
| Variable | Default | Req. | Descripción |
|---|---|---|---|
DATABASE_URL | — | ✅ | Connection string de PostgreSQL |
OPENAI_API_KEY | — | ✅ | Embeddings (siempre OpenAI) |
AUTH_SERVICE_URL | — | ✅ | URL del ajolote (mismo que usa el front) |
AUTH_APP_ID | aura | — | App id en ajolote |
LLM_PROVIDER | openai | — | openai | groq | cerebras | mistral | mock |
LLM_MODEL | gpt-4o-mini | — | Modelo de chat |
LLM_API_KEY | = OPENAI_API_KEY | — | API key del provider de chat |
LLM_BASE_URL | — | — | Base URL para providers no-OpenAI |
EMBEDDING_MODEL | text-embedding-3-small | — | Modelo de embeddings (runtime) |
SIMILARITY_THRESHOLD | 0.78 | — | Umbral de hit en memory_embeddings. Solo semilla del primer boot (ver abajo) |
APPROVED_THRESHOLD | 0.85 | — | Umbral para ejemplos aprobados. Solo semilla del primer boot |
KNOWLEDGE_THRESHOLD | 0.60 | — | Umbral de hit en knowledge_base. Solo semilla del primer boot |
QUERY_LATENCY_BUDGET_MS | 500 | — | Timeout de la query RAG (ms) |
CUSTOM_LLM_TOKEN_VOICE | — | — | Bearer del agente de voz de ElevenLabs. Vacío = POST /chat/completions rechaza todo |
CUSTOM_LLM_TOKEN_WHATSAPP | — | — | Bearer del agente de WhatsApp. Distinto del de voz: el token es el que elige el canal |
PORT | 3000 | — | Puerto HTTP/WS |
RETELL_API_KEY / RETELL_SECRET | — | — | Retell (transferencias / auth webhook) |
PATIENT_LOOKUP_URL | — | — | Base URL del sistema de pacientes para el lookup (AURA_SHARED_SECRET es el secreto compartido de ese endpoint). Vacío = el agente funciona, pero sin filtro de equipo en el RAG, y SecurityBootLogger lo avisa al boot. Antes se llamaba EXISTING_BACKEND_URL, que se sigue leyendo como fallback |
PATIENT_LOOKUP_PROVIDER | oxitesa | — | Qué adaptador contesta: oxitesa | mock | disabled. Un valor desconocido rompe el arranque en vez de degradar en silencio. Sin setear queda oxitesa, que contesta "sin conexión" hasta que se cargue la URL y funciona apenas se carga. mock resuelve desde un roster en memoria; disabled apaga la consulta y es la salida de emergencia |
PATIENT_LOOKUP_MOCK | — | — | JSON [{ dni, phones, equipment }] que pisa el roster por defecto del mock. Los teléfonos se normalizan al cargar, así que se escriben como venga |
DEEPGRAM_API_KEY | — | — | STT (transcripción en vivo y de audios subidos en /anfibios) |
RABBITMQ_URL | — | — | Cola de transcripción de audios (solo local, ver docker-compose). Sin ella la subida da 503 |
TRANSCRIPTION_CONCURRENCY | 1 | — | Jobs de transcripción en paralelo (prefetch) |
TRANSCRIPTION_MAX_ATTEMPTS | 3 | — | Reintentos antes de mandar el job a la DLQ |
Cambiar de provider LLM es solo .env (sin recompilar), ej. LLM_PROVIDER=mock para dev
sin API keys.
Ahora son ajustes runtime con scope por canal. La ENV se usa
una sola vez: en el primer boot, para sembrar la fila scope='global' de cada uno. Después de
eso, cambiar KNOWLEDGE_THRESHOLD en el .env no hace nada — hay que tocar el setting (panel de
/anfibios/settings, o PATCH con ?scope= para un canal). Siguen declaradas para que el primer
arranque de cada entorno conserve el valor que ya tenía.
Frontend (aura-front)
pnpm install
cp .env.example .env.local
pnpm dev # → http://localhost:5173
Variables de entorno (frontend)
VITE_API_BASE_URL=http://localhost:3000 # backend de Aura
VITE_AUTH_URL=http://localhost:5173 # dev server (proxy a ajolote)
VITE_AUTH_PROXY_TARGET=http://100.64.188.115:3010 # ajolote real (dev)
VITE_COPILOT_WS_URL=ws://localhost:3000/copilot/TU_AGENT_ID
VITE_USE_MOCK=false # true = levanta sin backend
VITE_SHOW_SOURCE_FILTER=true # muestra filtro de origen en paneles
En dev el front le pega a su propio dev server y Vite proxya /api/auth al ajolote
real (VITE_AUTH_PROXY_TARGET). Esto es porque la cookie de sesión sale SameSite=Lax y
no viaja cross-site. Sin el proxy, get-session devuelve null y nunca quedás logueado.
Detalle en Autenticación.
Probar el Custom LLM por curl (y los acentos)
POST /chat/completions es el endpoint que llama ElevenLabs, y en dev se prueba con curl
imitando ese request. El body tiene que ser UTF-8. Escribir el JSON con acentos directo en la
línea de comandos en Windows manda los bytes en cp1252 (á = 0xE1), que no es UTF-8 válido: el
parser los reemplaza por <?> y el turno llega dañado — pierde el match de equipos en el RAG y le
entra ruido al modelo. El backend loguea un [TURN] ... received invalid UTF-8 cuando eso pasa, así
que si lo ves en el log, el problema está en el cliente, no en Aura.
La forma segura es un archivo y --data-binary:
# el archivo queda en UTF-8 real, sin depender del codepage de la consola
cat > /tmp/turno.json <<'JSON'
{"messages":[{"role":"user","content":"el M50 tira H08 y la cánula está doblada"}]}
JSON
curl -X POST localhost:3000/chat/completions \
-H "Authorization: Bearer $CUSTOM_LLM_TOKEN_WHATSAPP" \
-H 'Content-Type: application/json' -H 'x-conversation-id: demo-1' \
--data-binary @/tmp/turno.json
Y para que la salida se lea bien (los á que se ven como á o ? son la consola, no los
datos): chcp 65001 en la terminal de Windows, y PYTHONIOENCODING=utf-8 si inspeccionás las
respuestas con Python. Los datos ya están bien: el endpoint responde
application/json; charset=utf-8, el SSE declara text/event-stream; charset=utf-8 y en Postgres
á está guardado como \303\241.
Ajolote (auth)
El backend y el front tienen que apuntar al mismo ajolote (AUTH_SERVICE_URL ==
VITE_AUTH_PROXY_TARGET), o los roles no matchean. La administración de usuarios y roles
(crear tenant, usuarios, asignar roles) se hace contra ajolote, no desde Aura —
ver Autenticación → Roles.