decisión técnica · para el equipo

Propuesta de cómo podemos construir llamaxmi

Una propuesta de stack a debate, pieza por pieza, con el porqué de cada elección y alternativas reales en cada punto para decidir en equipo.

LA PROPUESTA, EN UNA FRASE

Un solo proyecto: Next.js todo-en-uno

Next.js (web + app + admin + API, en un proyecto) + TypeScript + Tailwind 4 + shadcn · PostgreSQL + Drizzle + Better‑Auth · worker Node para las llamadas · VPS propio + Dokploy · Rust para el motor de voz más adelante.

Tras analizarlo, la recomendación cambia: el proyecto quiere ser pro y todo-en-uno, sin tener que migrar dentro de unos meses. La velocidad de montaje no es la prioridad — lo es no arrepentirnos. Por eso gana Next.js frente a la vía anterior (Astro para la web + React/Vite para la app, en proyectos separados). Lo único que se mantiene aparte, con razón, es el worker que ejecuta las llamadas. La decisión gorda que sigue abierta —montar todo en nuestro VPS o tirar de Supabase— se cierra el jueves.

CÓMO ENCAJA TODO

Un proyecto, más un worker aparte para las llamadas

Ya no son tres piezas separadas. La web pública, la app privada, el panel de admin y la API son páginas de la misma web, en un solo proyecto Next.js. Fuera de él queda solo el worker que ejecuta las llamadas — porque un proceso largo de audio no cabe dentro de la petición de una página.

▣ Un proyecto Next.js · llamaxmi.com
Next.js + TypeScript · Tailwind 4 + shadcn · Route Handlers (API)
Web pública, app privada, admin y API, todo junto. Son rutas de la misma web: llamaxmi.com/ (landing, SEO), /app (tras el login), /app/admin (panel). Un repo, un despliegue, login compartido, componentes compartidos, sin CORS interno.
encola trabajos de llamada (cola)
◆ Worker Node · ejecuta las llamadas
Node + BullMQ + Redis (cuando escale)
Proceso aparte, con razón: las llamadas son procesos largos (audio, colas, reintentos, transcripciones) y no van dentro del request de una página web. Toma trabajos de la cola, los ejecuta y devuelve el estado a la base de datos.
[fase 3] motor de voz en tiempo real
◈ Motor de voz · fase 3
Rust / LiveKit (servicio independiente)
El audio en tiempo real llega más adelante, como servicio propio. El diseño y el resto del sistema solo necesitan que exista — habla con el worker por una frontera clara.

Regla de oro: web, app y admin son la misma web (un solo Next.js). Lo único que vive fuera es el worker/motor de voz — todo lo demás está en un proyecto.

LA DECISIÓN DE LA APP

Next.js, no dos proyectos separados

La pregunta ya no es «qué framework para la app», sino «un proyecto o dos». La vía anterior partía la web (Astro) y la app (React + Vite) en dos proyectos separados. Next.js las une en uno: web pública, app privada, admin y API bajo el mismo techo. Para un producto que quiere ser pro y no migrar en unos meses, unificar gana.

CriterioNext.js (un proyecto)Astro + React/Vite (separado)Gana
Todo en uno (web + app + admin + API)Un repo, un despliegueDos proyectos, dos desplieguesNext.js
SEOExcelente + indexa páginas dinámicas (p.ej. /negocios/…)Bueno solo en la landing; la app (SPA) no indexaNext.js
Login compartidoUno solo, mismo proyectoPartido entre dos appsNext.js
ComponentesCompartidos entre web y appDuplicados en cada proyecto, para siempreNext.js
DespliegueUno, sin CORS internoDos, con frontera y CORS entre ellosNext.js
Riesgo de migrar en el futuroYa es el destino: no se migraAlto — el día que la app necesite SEO o previews, toca migrar a NextNext.js
Web 100% estática puraMuy rápida (diferencia marginal)Un pelín más rápida en páginas estáticasSeparado (marginal)
Control fino / desacople a gran escalaMenos separado por diseñoMayor desacople y control finoSeparado (a gran escala)

Siendo honestos con la vía separada: la web estática de Astro es un pelín más rápida en páginas 100% estáticas (diferencia marginal, no se nota en Google); separar da mayor desacople y control fino (útil a gran escala, no ahora); y React + Vite «a pelo» tiene menos magia que el App Router de Next (menos cosas que aprender), pero a cambio no unifica nada. El coste permanente de separar —dos proyectos, dos despliegues, autenticación partida, CORS y componentes duplicados para siempre— pesa más que ese beneficio pequeño.

El riesgo de migrar lo tiene la opción separada, no Next. Con Astro+Vite, el día que necesites SEO en páginas de la app, enlaces compartibles con vista previa (WhatsApp) o SSR dinámico, con Vite no puedes y tendrías que migrar a Next. Con Next ya lo tienes.

CÓMO ENCAJAN FRONT Y BACK

El front y la API viven en el mismo proyecto

Con Next.js no hay dos servicios que se hablen para el CRUD: es un solo proyecto.

Las pantallas y la API viven en el mismo Next.js: la lógica de datos son Route Handlers dentro del proyecto. No hay dos servicios cruzando una frontera para leer y escribir en la base de datos — no hay CORS interno ni «app llama a un backend aparte» para el CRUD.

Next.js (un proyecto) páginas ──► Route Handlers (API) ──► PostgreSQL web · app · admin el CRUD, misma base de código │ encola trabajo ──────┤ ▼ Worker Node ──► [fase 3] motor de voz (Rust) (procesos largos: audio, colas, reintentos)
  • Next.js hace las pantallas y la API en la misma base de código. Para el CRUD no hay frontera que cruzar.
  • La frontera clara queda solo con el worker: la web le encola trabajos de llamada y el worker los ejecuta aparte.
  • El motor de voz en Rust (fase 3) habla con el worker por esa misma frontera — el resto del sistema no se toca.

La separación se guarda para donde importa: los procesos largos de audio no caben en la petición de una página, así que el worker vive fuera. Todo lo demás está unido.

OPCIÓN A · MONTARLO NOSOTROS

Todo en nuestro VPS, controlado por nosotros

Con el framework ya decidido (todo en un Next.js), esta opción es sobre quién aloja la base de datos y el login: los montamos nosotros mismos, en nuestro VPS. La API vive dentro del propio Next.js (Route Handlers); nosotros ponemos el PostgreSQL y el auth. Más control, cero dependencia de plataformas externas, y todo vive en un sitio que es nuestro. (La otra vía, Supabase, está más abajo — Opción B.)

Las piezas que necesitamos

Para quéQué usamos
Servidor / APINext.js + TypeScript · Route Handlers (dentro del mismo proyecto)
Base de datosPostgreSQL en el VPS
Hablar con la BD (con tipos)Drizzle ORM + Drizzle Kit (migraciones)
Login de usuarios (auth)Better‑Auth o Lucia (TypeScript, se integran con nuestro Postgres) · Auth.js también vale
Contraseñas y sesionesargon2 (hash) + JWT (jose)
Estado en vivo (los 9 estados)WebSocket propio: Socket.IO (reconexión y salas de serie)
Validar lo que entra por la APIZod
Colas de trabajo (resúmenes, reintentos)Tabla jobs en Postgres al principio · BullMQ + Redis si escala
LogsPino

Lo que asumimos al hacerlo nosotros

Montar el servidor propio significa que el login, el tiempo real, los backups y el mantenimiento del servidor son trabajo nuestro — justo lo que una plataforma como Supabase da hecho. No es más difícil, son más piezas que mantenemos. A cambio: control total y sin atarnos a nadie.

CÓMO SE PUBLICA

Despliegue con GitHub + Dokploy

Todo se despliega solo desde GitHub: subes el código y se publica. Para gestionarlo en nuestro VPS usamos Dokploy — una herramienta open-source que se instala en el servidor y hace de «panel de control» tipo Vercel/Heroku, pero en nuestra máquina.

Con Dokploy tenemos…Sin salir de nuestro VPS
Despliegue automático al hacer push a GitHub
La base de datos PostgreSQL (un clic)Sí, gestionada por Dokploy
HTTPS automático (certificados)Sí (Traefik por debajo)
Backups programados de la BD
Ver logs, reiniciar, variables de entornoSí, desde su panel

En una frase: Dokploy nos da la comodidad de Vercel/Heroku pero en nuestro propio servidor. Empujas a GitHub, se despliega solo, y la base de datos, el HTTPS y los backups los gestiona él — sin renunciar a que todo sea nuestro.

OPCIÓN B · LA VÍA RÁPIDA

Supabase, si queremos ir más rápido

Un atajo válido: Supabase es PostgreSQL, con lo demás ya montado.

Supabase no es otra base de datos: por dentro es el mismo PostgreSQL, pero con el login, el tiempo real y los permisos ya hechos. Es la vía rápida — nos ahorra justo las piezas que en nuestro stack montamos a mano.

PiezaNuestro VPSSupabase
Base de datosLa montamos y gestionamosEl mismo Postgres, gestionado
LoginLo montamos (Better-Auth/Lucia)Incluido
Tiempo realWebSocket propio (Socket.IO)Incluido (Realtime)
Backups y servidorNuestros (vía Dokploy)Gestionado
Control / dependenciaTotal, cero dependenciaAlto, en su plataforma

Cuándo tiraría de Supabase: si en fase 1 queremos llegar rapidísimo sin dedicar tiempo a montar login y tiempo real. Y como por debajo es Postgres estándar, migrar a nuestro VPS más tarde es llevarse la base de datos tal cual — sin quedarnos atrapados. Nota: Supabase también se puede auto-alojar en nuestro propio VPS (es open-source), un punto intermedio entre las dos opciones.

La decisión que queda abierta (jueves): ¿montamos todo nosotros en el VPS (más control, más trabajo inicial) o arrancamos con Supabase (más rápido, menos control)? El diseño y el frontend son idénticos en ambos casos.

¿HAY ALGO MEJOR?

Otras vías que valoramos

Ejercicio honesto: además de la vía elegida (Next.js todo-en-uno), estas son las opciones que estuvieron sobre la mesa.

Astro + React/Vite separado (evaluada y descartada)

▲ Mejora: la web estática de Astro es un pelín más rápida en páginas 100% estáticas (diferencia marginal, no se nota en Google); separar da mayor desacople y control fino (útil a gran escala); y React + Vite «a pelo» tiene menos magia que el App Router de Next (menos cosas que aprender).

▼ Cuesta: dos proyectos, dos despliegues, autenticación partida, CORS y componentes duplicados para siempre. Y el riesgo de migrar lo tiene ella: el día que la app necesite SEO, previews compartibles o SSR dinámico, con Vite no puedes y toca migrar a Next.

Era la propuesta de partida; la descartamos porque cambia un beneficio pequeño (SEO de la landing) por complejidad permanente. Con Next.js ya tenemos todo eso sin partir nada.

Elixir / Phoenix

▲ Mejora: su tecnología nació literalmente para telefonía; un proceso por llamada con recuperación automática si algo falla; el tiempo real viene de serie. Conceptualmente, el encaje más elegante.

▼ Cuesta: nadie del equipo lo conoce, contratar es difícil en España, y su superpoder queda desaprovechado si solo orquestamos un proveedor externo.

Lo elegiría si construyéramos el propio motor de voz, o si hubiera un senior de Elixir en el equipo.

Supabase-first (la alternativa fácil)

▲ Mejora: login completo sin escribir una línea, base de datos gestionada y tiempo real: la app recibe los 9 estados en vivo sin que montemos nada. Recorta semanas.

▼ Cuesta: dependemos de su plataforma (mitigable: es Postgres estándar, migrable); menos control que en nuestro VPS.

Si en fase 1 queremos velocidad máxima. Detallada arriba en «Opción B · la vía rápida».

Rust para el motor de voz (más adelante)

▲ Mejora: cuando montemos el servidor de la IA de voz, Rust brilla ahí: concurrencia masiva barata, sin fugas de memoria en llamadas largas, latencia predecible.

▼ Cuesta: curva de aprendizaje seria; los SDK de voz son Node-first. No aporta en el CRUD de fase 1.

Como fase posterior, no en el arranque: primero Node + Postgres, y Rust entra solo en el motor de voz cuando toque.

RESUMEN

Lo que propongo, para debatirlo

Esto es mi propuesta de partida — tecnologías con las que estoy cómodo y que encajan bien. Nada está decidido: lo llevo a la reunión para debatirlo en equipo. Cada pieza tiene su alternativa (arriba), así que descartad, cambiad o confirmad lo que veáis.

Mi propuesta, pieza por pieza:

Next.js todo-en-uno (web + app + admin + API) TypeScript Tailwind 4 + shadcn/ui PostgreSQL + Drizzle + Better-Auth Worker Node aparte para las llamadas Rust para el motor de voz (fase posterior)

La decisión que más pesa:

¿Todo en nuestro VPS o arrancar con Supabase?

Quién gestiona la base de datos, el login y el tiempo real: montarlo nosotros en el VPS (más control, más trabajo inicial) o Supabase de atajo (más rápido, dependemos de su plataforma). El framework ya está decidido: todo en un Next.js, con el worker de llamadas aparte y Rust entrando después, solo cuando montemos el motor de voz. El despliegue lo haría desde GitHub con Dokploy — eso también a debate, no es un mandato.

Para el diseño, da igual qué se decida: el frontend y las pantallas son idénticos con cualquiera de las opciones. Esto es del carril de servidor y lo cerramos entre todos.

DETALLE QUE AHORRA UN SUSTO

Un aviso sobre shadcn + Tailwind

La versión actual de la herramienta de shadcn ya no soporta Tailwind 3 (asume Tailwind 4). Como arrancamos un Next.js nuevo desde cero, esto no nos da problema: vamos directos a Tailwind 4 + shadcn@latest, sin fijar versiones ni el lío del pin.

Los tokens de color del sistema de diseño ya están en formato shadcn — se integran con un paso de conversión (a canales de color), no «pegando tal cual». Todo el detalle está en ARQUITECTURA.md §3.

EL MOTOR DE VOZ, EN CORTO

Y para la llamada: LiveKit + Gemini

La web no hace la llamada ella misma (una página no puede quedarse minutos hablando por teléfono). La ejecuta un worker aparte que orquesta tres piezas:

El worker fase 3
Node · toma trabajos de la cola
Coge la petición de la cola, arranca la llamada y va escribiendo su estado en la base de datos.
El motor de voz fase 3
LiveKit (sala en tiempo real) + Gemini (el cerebro)
LiveKit transporta el audio en directo; Gemini es el agente que escucha, habla y ejecuta las tools (parar y preguntarte, apuntar el resultado…).
La salida al teléfono fase 3
Zadarma / proveedor SIP
El puente a la red telefónica real. En esta fase las pruebas son por navegador; el teléfono de verdad entra en la fase 3.

El detalle de qué ocurre paso a paso durante la llamada —y sobre todo cómo la IA te pregunta en directo cuando se topa con un límite— está explicado con su diagrama en Producto v2 → «Cómo funciona por dentro». Aquí solo señalamos las piezas.