Backend En producción

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.

Rol
Fundador y desarrollador único
Desde
Febrero de 2026
Backend
Java · Spring Boot · PostgreSQL
Clientes
React · React Native (Expo)
Ver vecinly.comLa web en producción, en 11 idiomas

En cifras

~590casos de uso, de dar de alta una comunidad a cobrar su suscripción
~230modelos de dominio que guardan las reglas de negocio, sin depender de nada
11idiomas y 11 países, cada uno con su ley de propiedad horizontal
~4.680tests en el backend, contra PostgreSQL real

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.

Figura 0. La portada de vecinly.com, con su animación. La web se prerenderiza en los 11 idiomas para que la lean buscadores y motores de IA, que no ejecutan JavaScript.

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.

Lo que la pone en marcha Lo que usa Adaptadores traducen de fuera a dentro Casos de uso ~590 en total puertos 165 de salida Dominio ~230 modelos Web App iOS App Android Cloudflare Tareas ~40 programadas REST Canal en vivo Planificador Persistencia PostgreSQL Cifrado KMS Pagos Stripe Facturación Holded Identidad Identidad Archivos Almacenamiento Avisos Correo y push las dependencias apuntan hacia dentro
Figura 1. La API de Vecinly, por dentro. Las reglas de negocio viven en el dominio, que no depende de nada. Los casos de uso las orquestan y hablan con el exterior solo a través de puertos; los adaptadores, en el borde, conectan esos puertos con la web, las apps y los proveedores. Cifras aproximadas del código.

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.

CapaQué contieneRegla
DominioEntidades y reglas de negocio (~230 modelos)Sin Spring ni base de datos
Casos de usoLas acciones de la aplicación (~590 en total)Hablan con fuera solo por puertos
AdaptadoresREST, WebSocket, PostgreSQL, Stripe, Holded, KMSImplementan 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.

Monolito · un solo bloque Servicios · el núcleo organiza la comunicación Núcleo Chat Incidencias Contabilidad Reservas Documentos Cobros Juntas siempre encendido escala a cero
Figura 2. Romper el monolito. El bloque se parte por las fronteras entre dominios. Una pieza, el núcleo, queda en el centro y organiza la comunicación; cada servicio habla solo con él y escala a su manera.

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:

ÁreaCasos de uso principalesOperaciones
Comunidad y altasCrear la comunidad con un asistente, dar forma a portales y viviendas, importar coeficientes, invitar a vecinos e inquilinos, asignar administrador~130
Mantenimiento y proveedoresRegistrar y seguir incidencias, asignar tareas, pedir trabajos, recibir y aprobar presupuestos y facturas de proveedores~120
ContabilidadPresupuesto anual, cuotas por coeficiente, grupos de gasto, movimientos y saldo~65
Suscripción y cobrosContratar un plan, cambiarlo o cancelarlo, cobrar y emitir la factura~55
ComunicaciónChat, comunicados, avisos de emergencia y moderación~40
Juntas y decisionesConvocar juntas, votaciones con validez legal y encuestas~35
DocumentosCarpetas de la comunidad y de cada propietario, documentos legales~30
Zonas comunesReservas y plazas de garaje~20
PorterosAlta, 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:

Vecinly Residentes Gestión profesional Reservar una zona comúnRegistrar una incidenciaVotar en una juntaConvocar una juntaAprobar un presupuestoEmitir las cuotasEnviar un presupuestoSubir una factura Vecino Presidente Administrador Proveedor
Figura 3. Algunos casos de uso esenciales y quién los usa: los residentes a la izquierda, la gestión profesional a la derecha. Cada perfil solo ve los suyos.

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.

Figura 4. El asistente para dar de alta una comunidad, rellenándose solo: país, tipo de edificación, datos, estructura, resumen y plan. Es el componente real de la plataforma, con una comunidad inventada.
Dashboard de contabilidad: saldo en banco, ingresos y gastos previstos, fondo de reserva y previsión anual de tesorería.Presupuesto anual: gastos recurrentes, gastos puntuales e ingresos extra, con sus partidas.Cuadrante de pagos: cada vivienda y su cuota de cada mes, pagada, pendiente o vencida.
Figura 5. El panel de contabilidad, pestaña a pestaña: dashboard, presupuesto anual y cuadrante de pagos. Capturas de la comunidad de ejemplo de vecinly.com, con datos inventados.

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:

CapaCómo
AutorizaciónCada ruta declara qué rol y qué plan la permiten; lo que no declara nada, se deniega
ConsultasCada consulta va acotada a una comunidad, y los índices están pensados para eso
CifradoPrácticamente todo se guarda cifrado, con la clave de su comunidad
Tiempo realLas 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.

Clave maestra nunca sale del KMS Clave · comunidad A Datos de la comunidad A vecino · 9f3a… Clave · comunidad B Datos de la comunidad B vecino · 7d21…acta · c5e9…cuota · 1a6f… Clave · comunidad C Datos de la comunidad C vecino · b4d0…acta · 0e7c…cuota · 93a2… cuota · AAD = A 9f3a·c1e7·08b2 1.240,00 € no descifra cifrado autenticado · el identificador de la comunidad forma parte del cifrado
Figura 6. Una clave por comunidad. Prácticamente todo se guarda cifrado, y un dato cifrado de una comunidad no sirve en otra: el identificador de la comunidad forma parte del cifrado. Si la clave de sistema no llega del KMS, la aplicación no arranca.

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.

Origen ¿desde dónde llega? Volumen límite de peticiones Identidad ¿quién es? Regla sin regla, se deniega Permiso ¿rol y plan? Clave la de su comunidad Sus datos cifrados Laura · vecina ver sus cuotas Una petición que puede pasar
Figura 7. El ciclo de una petición. Antes de tocar un dato, cada petición pasa seis controles. La que tiene permiso llega a los datos de su comunidad, que se descifran con su clave; a la que le falta el rol o el plan, la detiene el control de permiso.
Un caso de uso, de principio a fin Vecino API Caso de uso Datos alt [no le corresponde] registrar una incidencia ¿quién es y qué puede hacer? denegado guardar, cifrado, en su comunidad incidencia registrada
Figura 8. Un caso de uso, de principio a fin. Las reglas viven en el caso de uso: la web y las dos apps pasan por el mismo sitio.

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.

no sí sí no sí no Llega un aviso de Stripe Guardar y responder con clave única Encolar el trabajo real Procesar aparte en segundo plano Activar el plan de la comunidad Emitir la factura en Holded Rechazar no es de Stripe Descartar sin cobrar de nuevo Reintentar si sigue, a revisión ¿Firma válida? ¿Ya lo había recibido? ¿Se aplica bien? aviso nuevo
Figura 9. Cobrar una sola vez. Un aviso nuevo llega hasta el plan activo y la factura; si Stripe repite el mismo aviso, la clave única lo detiene antes de tocar nada. Si al procesar algo falla, se reintenta, y si sigue fallando pasa a revisión.

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.

Un pago y su factura Administrador Stripe API Caso de uso Holded pagar la suscripción pago confirmado ¿ya lo había recibido? activar el plan emitir la factura factura
Figura 10. Un pago y su factura. Solo se factura un pago confirmado, y un aviso repetido no se aplica dos veces.

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.

Descargar en App Store Disponible en Google Play
App Vecinly: resumen de la comunidad con incidencias, avisos y tareas. Resumen
App Vecinly: reservas de zonas comunes en un calendario. Reservas
App Vecinly: tareas de mantenimiento por estado. Tareas
App Vecinly: contabilidad de la comunidad con saldo y movimientos. Contabilidad
App Vecinly: chat entre vecinos de la comunidad. Chat
Figura 11. La app de Vecinly con datos de ejemplo: resumen, reservas, tareas, contabilidad y chat.

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.

1 · Pasa algo 2 · Se guarda 3 · Se publica 4 · Se reparte 5 · Llega App o web una acción API caso de uso PostgreSQL el cambio, guardado Cola eventos evento Consumidor a quién y por dónde Firebase push al móvil Correo a su bandeja Canal en vivo a la app abierta El evento sale después de guardar: nunca se avisa de algo que no ha quedado registrado.
Figura 12. Cómo llega un aviso. La API guarda el cambio y, ya guardado, publica un evento en una cola. Un consumidor lo recoge aparte, decide a quién avisar y por qué canal, y lo entrega sin que quien hizo la acción tenga que esperar.

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.

Clientes Web11 idiomasApp VecinlyiOS · AndroidApp Vecinly ProiOS · Android inicio de sesión Cloudflare DNS · CDN · cortafuegos Nube en la UE Red privada HTTPS API · Java contenedores serverless · tareas programadas solo por la red privada PostgreSQL gestionada · sin dirección pública Servicios gestionados KMSclave maestra Secretosfuera del código Colastrabajo en segundo plano Archivosdocumentos y fotos Despliegue CI: tests → imagen → registro privado → contenedores, solo desde una versión etiquetada Infraestructura como código; la base de datos queda fuera y ningún cambio puede borrarla Servicios externos Firebaseidentidad y push Stripecobros Holdedfacturas legales Correotransaccional
Figura 13. Vecinly en producción. Los clientes entran por Cloudflare hasta la API. La API y la base de datos comparten una red privada: la base de datos no tiene dirección pública y solo la API llega a ella. Al lado, los servicios gestionados de la misma nube; fuera, Firebase, Stripe, Holded y el correo.
QuéCómo
Tests~4.680; los de integración, contra PostgreSQL real en contenedores, nunca contra una base simulada
Antes de desplegarLos unitarios y los de integración críticos: dinero, suscripciones, permisos y la contabilidad
Mutation testingSobre suscripciones y dominio, con un umbral mínimo que no se puede bajar sin que falle el build
Base de datosFuera de la infraestructura como código: ningún cambio de infraestructura puede recrearla ni borrarla
EsquemaSe 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.
Escríbeme sobre este proyecto →