SaaS B2B · Reclutamiento + RRHH

TalentFlow

ATS + HRIS multi-tenant en una sola plataforma

Plataforma SaaS que une el seguimiento de candidatos (ATS) y la gestión de empleados (HRIS) en una sola aplicación multi-tenant. Cada organización vive bajo su propio slug en el path y opera aislada por Row-Level Security a nivel de PostgreSQL — no por filtros aplicativos —, así que un bug de query no expone datos de otro cliente. La misma plataforma cubre publicar una vacante, recibir postulaciones, mover candidatos por el pipeline, contratarlos y, desde ahí, gestionar instituciones, áreas, cargos, ausencias y auditoría.

TalentFlow

El reto

Un consultorio mediano o una red educativa no necesita un ATS gigante de empresa Fortune 500 ni un HRIS de hospital. Lo que necesita es una sola plataforma que reciba postulaciones desde un portal público, mueva candidatos por un pipeline ordenado, los convierta en empleados con un click y luego les gestione las ausencias, vacaciones y reposos médicos — sin licencias por usuario, sin tres herramientas distintas y sin spreadsheets en paralelo.

Y, sobre todo, sin riesgo cross-tenant: cada organización es un cliente distinto y los datos de un cliente no pueden filtrarse al otro por un bug de query.

La solución

TalentFlow es una plataforma SaaS multi-tenant donde cada organización vive bajo su propio slug en el path (talent.palosanto.com/<slug>/...) y opera aislada a nivel de base de datos mediante Row-Level Security de PostgreSQL — no por filtros aplicativos. La misma app cubre el ciclo completo: publicación de vacantes, postulación pública con consentimiento LOPDP, pipeline de candidatos, conversión a empleado y gestión continua de instituciones, áreas, cargos, ausencias y auditoría.

Multi-tenancy donde de verdad importa: en Postgres

  • Row-Level Security a nivel de motor: la aplicación se conecta con un rol talentflow_app sujeto a políticas que restringen filas por tenant_id; el rol superusuario sólo se usa para migraciones. Un bug de query no puede exponer datos de otro tenant — la base directamente no devuelve las filas.
  • Routing por path, un solo dominio: una sola URL pública, un solo certificado TLS, sin wildcard DNS ni cert wildcard. Middleware resuelve el slug del primer segmento e inyecta x-tenant-slug consumido por toda la app.
  • Flujos basados en token (verificación de correo, aceptar invitación, restablecer contraseña) viven al root y derivan el tenant del token, sin acoplarse al slug.

Onboarding self-service y autorización doble

  • Una organización se crea por /signup (nombre, slug, sector, primera sede, Owner). Verificación de correo con expiración de 24 h; mientras no se verifique, la cuenta no inicia sesión.
  • 6 roles (Owner, Admin, HR Director, Recruiter, Hiring Manager, Viewer) con un módulo central de permisos Can.X consultado tanto en server components (para ocultar UI) como en server actions (para autorizar mutaciones) — validación cliente y servidor.
  • Invitación por email con rol y mensaje personalizado, duración de 7 días y reenvío con un click; al aceptar, el usuario fija contraseña y se activa automáticamente.

ATS completo: vacante → postulación → pipeline → contratación

  • Vacantes con estados draft → active → paused → closed_filled → archived, asignación de recruiter, headcount, modalidad, tipo de contrato, experiencia mínima, rango salarial con visibilidad pública opcional, hard/soft skills, beneficios y deadline. Cada vacante tiene su propio slug para su URL pública.
  • Bolsa pública por tenant en /<slug>/empleos, con página dedicada por vacante y formulario de postulación que crea automáticamente el candidato registrando consentimiento LOPDP explícito (timestamp + texto exacto del consentimiento).
  • Pipeline auditable: etapas NEW → POTENTIAL → INTERVIEW → OFFERED → ACCEPTED más REJECTED. Cada movimiento se historiza con autor, fecha y comentario opcional. La vista por candidato muestra todas sus aplicaciones simultáneas.
  • Conversión candidato → empleado en un click: se crea el registro con EMP-NNNN autogenerado, se marca al candidato como contratado y se cierran las aplicaciones pendientes que estaban en OFFERED, todo en una transacción atómica.

HRIS desde el día uno

  • Datos completos del empleado con campos sensibles cifrados a nivel de aplicación (cédula, salario) usando APP_SECRET, asignación a institución, área, cargo, jefe directo, contrato y fecha de ingreso.
  • Estados active, on_vacation, on_medical_leave, inactive.
  • Bandeja de solicitudes con flujo pending → approved/rejected para vacaciones y reposo médico; HR Director es el aprobador por defecto y vive en su propia pantalla.

Auditoría, privacidad y correo

  • Cada cambio relevante genera un audit_event con snapshot del autor (nombre + rol al momento del evento), IP, user-agent y descripción legible. Bitácora paginada y filtrable desde la app.
  • LOPDP (Ecuador): consentimiento explícito al postular, derecho al olvido con flujo de borrado programado a 30 días (anonimización en vez de DELETE) y trazabilidad de quién lo solicitó.
  • Mailgun por HTTP API directa (sin SDK) para verificación, bienvenida, invitación y restablecimiento — plantillas HTML inline con fallback a texto plano, asunto y CTA por idioma del tenant.

Decisiones de operación que importan

  • Despliegue dockerizado de punta a punta: Postgres + Redis + Next.js con Compose para dev y prod, Nginx como reverse proxy delante de la app en producción.
  • Seed de demo interactivo (seed-demo.ts) que pregunta el slug del tenant a poblar y siembra ~140 filas distribuidas (instituciones, empleados, vacantes en estados mixtos, candidatos con aplicaciones, movimientos de pipeline, solicitudes y auditoría), todo parametrizado con el nombre comercial del tenant.
  • Rate-limit por (email, IP) sobre intentos de login y tokens de un solo uso con expiración independiente por tipo (verificación, invitación, reset).

Resultados

  • Una sola plataforma reemplaza la combinación habitual de spreadsheet de candidatos + spreadsheet de empleados + carpeta de Drive con CVs + grupo de WhatsApp para aprobar permisos.
  • Cero filtraciones cross-tenant posibles: el aislamiento vive en Postgres (RLS), no en la lógica aplicativa, así que un bug de query no expone datos de otro cliente.
  • Onboarding de un cliente nuevo en minutos: registrar la organización por /signup, verificar correo, sembrar data demo opcional, invitar al equipo.
  • URLs públicas estables y compartibles para la bolsa de empleo del cliente (talent.palosanto.com/<su-slug>/empleos), sin DNS adicional, sin certificado wildcard y sin coordinar infra en cada onboarding.
  • Pipeline auditable: cada movimiento de candidato deja huella nominativa con timestamp — sirve tanto para coaching del equipo de reclutamiento como para defensa ante auditorías laborales.

Métricas concretas (tiempo medio para llenar una vacante, candidatos procesados, % de aplicaciones que llegan al portal público vs. fuentes externas) — disponibles al avanzar la conversación con un cliente piloto.

Por qué importa

TalentFlow demuestra que multi-tenancy serio no se hace con un WHERE tenant_id = ? regado por toda la app, sino con aislamiento real en la base de datos. Es el mismo patrón que llevamos a las custom apps cuando un cliente nos pide una plataforma SaaS propia para sus propios clientes: RLS desde el primer commit, routing por path para evitar pesadillas de DNS y certificados, auth propio con tokens cortos y rate-limit, y un seed reproducible para que cualquier conversación comercial empiece con la app llena de datos creíbles.