Vecinly: un SaaS para comunidades de vecinos
Vecinly es la plataforma todo en uno para gestionar comunidades de propietarios: incidencias, reservas, contabilidad, encuestas, proveedores, tareas y comunicación. La construí entera y la opero yo: la API, la web, las dos apps y la infraestructura. Está en producción, con clientes de pago.
Ver vecinly.comLa web en producción, en 11 idiomasEn cifras
01
Contexto
El problema
Quien sufre la gestión de una comunidad es el presidente, que no cobra por ello, o el administrador de fincas, que lleva decenas de comunidades a la vez. Las incidencias se pierden en un grupo de WhatsApp, las cuentas viven en una hoja que solo entiende quien la hizo y nadie sabe en qué punto está nada sin preguntar.
Un producto para eso tiene que servir a perfiles muy distintos (vecinos, presidentes, administradores, proveedores y porteros) sin que ninguno vea lo que no le toca. Y como guarda actas, cuotas y datos personales de vecinos de 11 países, la seguridad es lo primero.
Qué hace
Unos 50 dominios de negocio, de los que seis sostienen el día a día de una comunidad:
- IncidenciasEn un tablero por estados: se abre, se asigna y se cierra, y quien la reportó ve en qué punto está.
- ContabilidadMovimientos con sus partidas y su saldo, en lugar de una hoja de cálculo.
- FacturaciónFacturas de la comunidad enlazadas con la contabilidad y con el cobro recurrente.
- ChatVecinos, presidente, administrador y proveedores hablan dentro del producto, en tiempo real.
- Encuestas y juntasConsultas de un clic, convocatorias y seguimiento de las reuniones.
- ReservasLa pista o el salón se reservan desde la app, con el calendario a la vista de todos.
02
Arquitectura
Tres clientes, una sola API
La web, la app de iOS y la de Android hablan con el mismo servicio. Las reglas de negocio y los permisos viven en un único sitio: una regla se escribe una vez y llega a los tres clientes, y la web y las apps no pueden comportarse de forma distinta.
Un monolito hexagonal, para esta fase
En un MVP lo importante es validar la idea: que el desarrollo sea ágil, que desplegar sea sencillo y que los costes se mantengan bajos. Un monolito bien construido, con las fronteras entre dominios claras, es una opción muy válida para eso: una sola base de código, un solo despliegue y nada de red entre las piezas. Los microservicios son la mejor opción cuando la situación lo requiere, y en esta fase habría sido una mala idea: mucha complejidad para un producto que todavía tenía que demostrar que alguien lo quería.
| Capa | Qué contiene | Regla |
|---|---|---|
| Dominio | Entidades y reglas de negocio (~230 modelos) | Sin Spring ni base de datos |
| Casos de uso | Las acciones de la aplicación (~590 en total) | Hablan con fuera solo por puertos |
| Adaptadores | REST, WebSocket, PostgreSQL, Stripe, Holded, KMS | Implementan los puertos; se cambian sin tocar el dominio |
El precio es la disciplina: las fronteras entre dominios dependen del diseño y de la revisión, no de la red, y hay tests de arquitectura que las vigilan. Esa disciplina es también lo que hace posible el paso siguiente.
Lo siguiente: hacia microservicios
La fase ha cambiado. Los objetivos ahora son tres: ganar usuarios, lo que hará crecer el consumo de recursos y su coste; obtener ingresos, que permitirán pagar una infraestructura mejor; y ampliar el producto con funciones que van más allá de validarlo. Con esos objetivos sí toca empezar a moverse hacia microservicios, y es lo que estoy planteando.
La complejidad y el trabajo aumentan, pero el uso de recursos mejora mucho, sobre todo en un entorno serverless. Con un monolito, toda la aplicación escala como un bloque aunque la mayor parte de lo que hace se use poco. Separada, cada pieza tiene su propia política: los servicios de uso esporádico escalan a cero y no cuestan nada mientras nadie los usa, y los que tienen que responder siempre al momento, como el inicio de sesión o el chat, mantienen una instancia encendida para evitar el arranque en frío. Como cada dominio ya habla con el resto a través de puertos, separar uno es cambiar un adaptador, no reescribir su lógica.
Los casos de uso, por áreas
Cada caso de uso responde a una pregunta concreta de un perfil concreto: «registrar una incidencia», «aprobar el presupuesto de un proveedor», «convocar una junta». Así se reparten:
| Área | Casos de uso principales | Operaciones |
|---|---|---|
| Comunidad y altas | Crear la comunidad con un asistente, dar forma a portales y viviendas, importar coeficientes, invitar a vecinos e inquilinos, asignar administrador | ~130 |
| Mantenimiento y proveedores | Registrar y seguir incidencias, asignar tareas, pedir trabajos, recibir y aprobar presupuestos y facturas de proveedores | ~120 |
| Contabilidad | Presupuesto anual, cuotas por coeficiente, grupos de gasto, movimientos y saldo | ~65 |
| Suscripción y cobros | Contratar un plan, cambiarlo o cancelarlo, cobrar y emitir la factura | ~55 |
| Comunicación | Chat, comunicados, avisos de emergencia y moderación | ~40 |
| Juntas y decisiones | Convocar juntas, votaciones con validez legal y encuestas | ~35 |
| Documentos | Carpetas de la comunidad y de cada propietario, documentos legales | ~30 |
| Zonas comunes | Reservas y plazas de garaje | ~20 |
| Porteros | Alta, asignación a comunidades y su día a día | ~20 |
Cifras aproximadas, contadas en el código en octubre de 2026. Una operación es una acción de la API; un caso de uso puede servir a varias.
No hace falta controlarlos todos al mismo nivel, pero sí los esenciales: qué perfil puede hacer qué, con qué datos y qué pasa después. Estos son algunos:
Dos piezas, por dentro
Detrás de cada pantalla hay muchos casos de uso a la vez. El alta de una comunidad es un flujo que encadena país, tipo de edificio, datos, estructura y plan, y de él salen las viviendas, el tier y la suscripción. La contabilidad es un panel donde se cruzan presupuesto, cuotas por coeficiente, gastos y pagos de cada vivienda.


03
Aislar cada comunidad
Una base de datos para todas, cuatro capas de aislamiento
Cientos de comunidades comparten base de datos, y un vecino nunca debe ver nada de otra. Una base o un esquema por cliente multiplicaría las migraciones y las conexiones por cada comunidad nueva. Por eso el aislamiento es lógico, por comunidad, y se sostiene en cuatro capas:
| Capa | Cómo |
|---|---|
| Autorización | Cada ruta declara qué rol y qué plan la permiten; lo que no declara nada, se deniega |
| Consultas | Cada consulta va acotada a una comunidad, y los índices están pensados para eso |
| Cifrado | Prácticamente todo se guarda cifrado, con la clave de su comunidad |
| Tiempo real | Las suscripciones al chat y a los avisos se deniegan por defecto |
Una clave de cifrado por comunidad
Se cifra prácticamente toda la información, también la que la normativa europea no obliga a cifrar: datos personales, conversaciones, documentos, cuentas. Solo queda sin cifrar lo que de verdad no tiene ninguna importancia. Una clave maestra que nunca sale del KMS envuelve las claves de datos, y cada comunidad tiene la suya. El identificador de la comunidad entra en el cifrado como dato asociado: un texto cifrado no sirve en otra comunidad.
Si la clave no llega del KMS, la aplicación se niega a arrancar: falla cerrada. Y para poder buscar un dato cifrado sin descifrarlo, se guarda a su lado una huella criptográfica (un índice ciego): las búsquedas y la unicidad van por ella.
El ciclo de una petición
Cada petición pasa varias comprobaciones antes de tocar un dato, y la clave de la comunidad solo se carga del KMS cuando hace falta.
04
Cobros
Stripe cobra, Holded factura
Vecinly cobra suscripciones por tramos de viviendas en varios países de la UE y tiene que emitir facturas legales. El cobro lo hace Stripe, con Stripe Tax. La factura la genera la aplicación en Holded al confirmarse el pago, con el IVA exacto que cobró Stripe, para cumplir VeriFactu. Las tarjetas no pasan nunca por mi servidor.
Cobrar una sola vez
Stripe reintenta sus avisos de pago y puede enviarlos desordenados. Así que al recibir uno solo se verifica su firma, se guarda con clave única y se responde enseguida; el trabajo real va aparte, por una cola.
Al procesar, se comprueba si ya se procesó y se marca como procesado en la misma transacción que aplica el cambio, para que un fallo a medias no deje un pago a medias. Y si la suscripción deja de estar sana, la comunidad vuelve al plan gratuito.
05
La app
Un código, dos apps
Vecinos y presidentes necesitan una app; administradores, proveedores y porteros, otra muy distinta. Las dos salen de un único código en Expo y React Native: Vecinly y Vecinly Pro, en iOS y Android, con el mismo inicio de sesión. Las apps no venden nada, por las normas de las tiendas: el pago y la creación de comunidades se hacen en la web.
Cómo llegan los avisos
Cualquier cosa que pasa en una comunidad puede generar un aviso. El recorrido es siempre el mismo: la API ejecuta el caso de uso y guarda el cambio en la base de datos; solo cuando está guardado publica un evento en una cola. Así quien hizo la acción no espera a que se envíe nada, y nunca llega un aviso de algo que no ha quedado registrado.
Un consumidor recoge el evento aparte, decide a quién de esa comunidad le toca enterarse y por qué canal lo prefiere cada uno, y lo entrega: notificación push a través de Firebase, correo, o al momento por el canal en vivo si tiene la app abierta. Si algo falla, el evento sigue en la cola y se reintenta.
06
Producción
Desplegar solo con una etiqueta
Un despliegue equivocado afecta a datos reales de comunidades. Por eso producción solo se despliega desde una versión etiquetada, y los tests se ejecutan sobre esa misma versión. Volver atrás es desplegar la etiqueta anterior.
| Qué | Cómo |
|---|---|
| Tests | ~4.680; los de integración, contra PostgreSQL real en contenedores, nunca contra una base simulada |
| Antes de desplegar | Los unitarios y los de integración críticos: dinero, suscripciones, permisos y la contabilidad |
| Mutation testing | Sobre suscripciones y dominio, con un umbral mínimo que no se puede bajar sin que falle el build |
| Base de datos | Fuera de la infraestructura como código: ningún cambio de infraestructura puede recrearla ni borrarla |
| Esquema | Se valida al arrancar contra el código; si no coincide, la aplicación no arranca |
Migraciones sin parar el servicio
Las migraciones son siempre idempotentes, y expandir y contraer van en versiones distintas: primero se añade y se rellena, y la restricción llega en la siguiente versión, porque una restricción que falla bloquea todos los despliegues posteriores.
07
Conclusiones
- La arquitectura acompaña a la fase. Un monolito bien partido por dentro era lo adecuado para validar el producto; esas mismas fronteras son las que ahora permiten repartirlo en servicios cuando crecer lo requiere.
- Aislar sin multiplicar bases. Cuatro capas y una clave por comunidad dan aislamiento real sin operar cientos de bases de datos.
- Cobrar es un problema de idempotencia. El doble cobro no lo evita la pasarela: lo evitan la clave única, la transacción y los barridos.
- Fallar cerrado. Sin clave no arranca, sin regla se deniega y sin suscripción sana se baja al plan gratuito.


