Initial commit: proyecto ContabilidadSaPolar completo

This commit is contained in:
root
2026-08-19 21:30:23 +00:00
commit 0d5c6f9512
232 changed files with 26730 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# Documentación de Sa Polar - Sistema de Gestión de Alquileres
## Guías técnicas
- [Arquitectura del sistema](tecnicas/arquitectura.md)
- [Referencia de la API REST](tecnicas/api.md)
- [Esquema de base de datos](tecnicas/base-de-datos.md)
## Planificación
- [Roadmap y planificación del proyecto](planificacion/roadmap.md)
## Manuales de usuario
- [Manual de usuario](usuario/manual.md)
+193
View File
@@ -0,0 +1,193 @@
# Planificación del Proyecto - Sa Polar
## Visión General
Sistema de gestión de alquileres desarrollado por fases incrementales. Cada fase añade funcionalidades completas y autónomas.
## Fases Completadas
### Fase 1 - MVP (Base del Sistema)
**Estado:** COMPLETADO
**Objetivo:** Sistema base funcional con operaciones CRUD esenciales y autenticación.
#### Módulos implementados
| Módulo | Funcionalidades |
|--------|----------------|
| **Autenticación** | Login con JWT, registro de usuarios, refresh token, roles (ADMIN, GERENTE, CONTABLE, VISUALIZADOR) |
| **Usuarios** | CRUD de usuarios, asignación de roles, activación/desactivación |
| **Propiedades** | CRUD con jerarquía (edificio → pisos), tipos y estados, historial de cambios de estado |
| **Inquilinos** | CRUD, búsqueda, personas físicas y jurídicas |
| **Contratos** | CRUD, asociación propiedad+inquilino, cambio automático de estado de propiedad al crear/terminar |
| **Recibos de Ingresos** | CRUD, categorías, registro de pagos, cálculo automático de retención IRPF, periodo |
| **Plantillas de Gastos** | CRUD, categorías, periodicidad, generación automática de recibos |
| **Recibos de Gastos** | CRUD, origen desde plantilla o manual, registro de pagos |
| **Documentos** | Subida/descarga polimórfica, validación de tipos por entidad, documentos obligatorios, componente reutilizable en todas las páginas |
| **Dashboard** | Resumen general con contadores y agregaciones financieras |
| **Notificaciones** | Sistema de notificaciones por usuario con marcado de lectura |
| **Infraestructura** | Docker compose (mysql + backend), Swagger/OpenAPI, script init.sql completo |
#### Tareas técnicas realizadas
- [x] Creación del proyecto Spring Boot multi-módulo
- [x] Configuración de Spring Security con JWT
- [x] Mapeo JPA de todas las entidades del dominio
- [x] Script init.sql con DDL y datos semilla
- [x] Configuración Docker con healthcheck de MySQL
- [x] Corrección de tipos de columna (TINYINT UNSIGNED → INT)
- [x] Corrección de palabra reservada `read` en MySQL
- [x] Corrección de LazyInitializationException con @Transactional
- [x] Generación correcta de hash BCrypt para admin
- [x] Configuración CORS para frontend
### Fase 2 - Recibos Automáticos e Incidencias
**Estado:** COMPLETADO
**Objetivo:** Automatizar la generación de recibos, gestión de incidencias y mantenimiento programado.
#### Módulos implementados
| Módulo | Funcionalidades |
|--------|----------------|
| **Incidencias** | CRUD completo, flujo de estados (SIN_REVISAR → TECNICO_AVISADO → REPARACION_PREVISTA → REPARADO), asignación de técnico, programación de reparación, prioridades |
| **Mantenimiento Programado** | CRUD, periodicidad configurable, cálculo de próxima ejecución, recordatorios |
| **Recibos** | Generación individual y masiva, numeración automática por serie fiscal, PDF con iText, envío por email con adjunto, log de envíos |
| **Reportes** | Informe mensual Excel (ingresos - gastos = balance) con Apache POI |
| **Tareas Programadas** | Generación mensual de recibos (día 1 a las 06:00), marcado de vencidos (diario 02:00), revisión de contratos próximos a vencer (día 1 a las 07:00) |
#### Tareas técnicas realizadas
- [x] Entidades ReceiptSeries y EmailLog
- [x] Servicios ReceiptService, PdfReceiptService, EmailReceiptService, ReportService
- [x] ReceiptScheduler con 3 tareas cron
- [x] ReceiptController con 7 endpoints
- [x] Configuración SMTP en application.yml
- [x] Tablas receipt_series y email_log en init.sql
- [x] Endpoints de reportes Excel
- [x] Frontend React + Vite + TypeScript completo
- [x] Páginas: Login, Dashboard, Properties, Tenants, Contracts, Incomes, Expenses, Incidents, Documents
- [x] Capa API con Axios e interceptor JWT
- [x] AuthContext con persistencia en localStorage
- [x] Layout con sidebar y navegación
- [x] Docker compose con servicio frontend (Nginx)
- [x] Proxy reverso en Nginx para /api/*
- [x] Compilación y build exitosos
- [x] ID visible en todas las tablas, detalles y formularios
- [x] Property Groups (Conjuntos) — entidad, CRUD backend, página frontend con propiedades asociadas
- [x] Sistema de documentos con validación tipo-entidad (V5 migration)
- [x] Componente DocumentUploader integrado en Contracts, Tenants, Properties, Incidents, Incomes, Expenses
- [x] Endpoint getDocumentTypesForEntity para filtrar tipos permitidos por entidad
- [x] CRUD completo de Inquilinos con validación de documentos (DNI/NIE/CIF)
- [x] Gestión dinámica de múltiples inquilinos en Contratos con creación inline
- [x] Acciones especiales: "Cobrar" en Ingresos, "Pagar" en Gastos, "Terminar contrato"
- [x] Componentes reutilizables: Modal, ConfirmDialog, Pagination, SortableHeader, Toast, EntityLink
- [x] Hook useSort para ordenación client-side con claves anidadas
- [x] Hook useEntityNavigation para navegación programática entre entidades
- [x] BankDataManager para gestión de datos bancarios de inquilinos (CRUD, validación IBAN)
- [x] Previsualización de documentos (PDF en iframe, imágenes JPEG/PNG/GIF/WebP)
- [x] Autocompletado de direcciones via datalist en formularios
- [x] Badges de estado y prioridad con colores en todas las tablas
- [x] Recepción de filtros desde Dashboard via location.state
- [x] Flyway configurado con 6 migraciones (V1-V6)
- [x] FlywayRepairConfig con estrategia por perfil (dev vs prod)
- [x] Perfiles application-dev.yml y application-prod.yml
- [x] TenantBankData: entidad, controller, repository, service, migración V6
## Fase 3 - Funcionalidades Avanzadas
**Estado:** PARCIALMENTE COMPLETADA
**Objetivo:** Mejoras en la experiencia de usuario y funcionalidades complementarias.
### Completado
| Módulo | Funcionalidades |
|--------|----------------|
| **Frontend Avanzado** | CRUD completo en 8 páginas (Properties, PropertyGroups, Tenants, Contracts, IncomeReceipts, ExpenseTemplates, ExpenseReceipts, Incidents, Documents) con patrón consistente ViewMode (list/detail/edit/create) |
| **Filtros y búsqueda** | Filtros desplegables + búsqueda por texto libre en todas las páginas de listado |
| **Paginación** | Paginación client-side con componente Pagination reutilizable (PAGE_SIZE = 20) |
| **Ordenación** | Cabeceras ordenables con hook useSort en todas las tablas |
| **Formularios** | Formularios de creación/edición completos con validación en todas las entidades |
| **Navegación cruzada** | Componente EntityLink para navegar entre entidades relacionadas |
| **Documentos adjuntos** | Componente DocumentUploader con drag & drop, previsualización (PDF/imágenes), descarga |
| **Datos bancarios** | Componente BankDataManager para gestión de IBAN de inquilinos con validación |
| **Notificaciones UI** | Sistema de Toast para feedback de acciones |
| **Flyway** | Configurado y funcionando con 6 migraciones (V1-V6), perfiles dev/prod |
| **Repositorio Git** | Inicializado con .gitignore completo, 5 commits |
### V11 — Refactor Financiero (COMPLETADO)
| Módulo | Funcionalidades |
|--------|----------------|
| **IncomeReceipt** | Nueva entidad con soporte de período, cuenta bancaria, domiciliación |
| **ExpenseTemplate** | Plantillas de gastos con periodicidad, importe fijo/variable |
| **ExpenseReceipt** | Recibos de gastos con origen desde plantilla o manual |
| **ExpenseScheduler** | Generación automática de recibos desde plantillas activas |
| **Refactor recibos** | PdfReceiptService, EmailReceiptService, ReceiptService, ReportService adaptados a nuevo modelo |
| **Frontend** | Páginas IncomeReceipts, ExpenseTemplates, ExpenseReceipts creadas |
| **Seed data** | Actualizado seed.sql con datos de demostración |
### Pendiente
| Módulo | Funcionalidades | Prioridad |
|--------|----------------|-----------|
| **Página Mantenimiento** | CRUD de mantenimiento programado ✅ COMPLETADO | Alta |
| **Mejoras Mantenimiento** | Reapertura de tareas, generación automática de gastos, diálogo de documentos al completar ✅ COMPLETADO | Alta |
| **Página Reportes** | Generación de informes Excel y gestión de recibos automáticos (backend existe, falta frontend) | Alta |
| **Página Notificaciones** | Gestión de notificaciones del usuario (backend existe, falta frontend) | Media |
| **Página Usuarios** | CRUD de usuarios y asignación de roles (backend existe, falta frontend) | Media |
| **Exportación** | Exportar listados a PDF/Excel desde el frontend | Media |
| **Funcionalidad avanzada frontend** | Terminar páginas IncomeReceipts, ExpenseTemplates, ExpenseReceipts con filtros y acciones completas | Alta |
| **Inventario** | Gestión de mobiliario y equipamiento por propiedad | Baja |
| **Candidatos** | Registro de interesados antes del contrato | Baja |
| **Temporada** | Alquileres por temporada con precios dinámicos | Baja |
## Fase 4 - Producción y Calidad
**Estado:** PARCIALMENTE COMPLETADA
**Objetivo:** Preparar el sistema para uso en producción con garantías de calidad.
### Completado
| Tarea | Descripción |
|-------|-------------|
| **Flyway** | Configurado con 6 migraciones SQL, perfiles dev (clean+repair+migrate) y prod (solo repair+migrate), FlywayRepairConfig |
| **Repositorio Git** | Inicializado, .gitignore completo (raíz + frontend), 5 commits |
### Pendiente
| Tarea | Descripción | Prioridad |
|-------|-------------|-----------|
| **Tests unitarios** | Tests para AuthService, ReceiptService, PdfReceiptService, ContractService, etc. | Alta |
| **Tests de integración** | Tests con H2 (ya incluido en pom.xml) o Testcontainers | Alta |
| **Pipeline CI/CD** | GitHub Actions para build y tests automáticos | Media |
| **Logs centralizados** | Estructura de logging consistente (SLF4J + Logback) | Baja |
| **Monitorización** | Health checks, métricas con Actuator | Baja |
| **SSL/TLS** | Certificados HTTPS para producción | Media |
| **Backups** | Script de backup automático de BD | Media |
| **Auditoría** | Tabla de auditoría para cambios sensibles | Baja |
## Notas sobre la Planificación
### Decisiones de arquitectura
- Se eligió **monolito modular** frente a microservicios por la simplicidad del dominio y para evitar complejidad operativa innecesaria.
- Se usa **init.sql + ddl-auto: validate** en lugar de Flyway para la fase inicial porque el esquema se define completamente desde el principio.
- El **frontend se separó del backend** desde el inicio para permitir desarrollo independiente y despliegue con Nginx.
- Los **recibos de ingresos** (tabla `income_receipts`) y **recibos de gastos** (tabla `expense_receipts`) se separan de las plantillas de gastos (tabla `expense_templates`) para mayor flexibilidad.
- Las **plantillas de gastos** permiten definir gastos recurrentes con periodicidad y generación automática mediante scheduler.
- Se usa **BCrypt** con Spring Security para contraseñas, con hash pre-generado para el usuario admin por defecto.
### Convenciones de código
- Nombres de tablas en **plural** y **snake_case**.
- Nombres de columnas en **snake_case**.
- Entidades JPA con **Lombok** (`@Getter`, `@Setter`, `@NoArgsConstructor`).
- Servicios con **inyección por constructor** (no `@Autowired` directo).
- Controladores con **inyección por constructor** y `@Valid` en request bodies.
- Paquetes organizados por **dominio de negocio** (no por capa técnica).
- URLs RESTful con **sustantivos en plural** y verbs HTTP semánticos.
+370
View File
@@ -0,0 +1,370 @@
# Referencia de la API REST
## Formato General
Todas las respuestas siguen el formato `ApiResponse<T>`:
```json
{
"success": true,
"message": "OK",
"data": { ... },
"timestamp": "2026-07-04T12:00:00"
}
```
Los errores usan el mismo formato con `success: false` y mensaje descriptivo.
## Autenticación
### `POST /api/auth/login`
Iniciar sesión.
**Request body:**
```json
{
"username": "admin",
"password": "admin123"
}
```
**Response (200):**
```json
{
"success": true,
"message": "OK",
"data": {
"accessToken": "eyJhbGciOi...",
"refreshToken": "eyJhbGciOi...",
"tokenType": "Bearer",
"userId": 1,
"username": "admin",
"role": "ADMIN"
}
}
```
### `POST /api/auth/register`
Registrar nuevo usuario.
**Request body:**
```json
{
"username": "usuario1",
"email": "user@example.com",
"password": "password123",
"fullName": "Usuario Ejemplo",
"phone": "600123456",
"roleName": "GERENTE"
}
```
### `POST /api/auth/refresh`
Renovar token de acceso.
**Request body:**
```json
{
"refreshToken": "eyJhbGciOi..."
}
```
## Usuarios (solo ADMIN)
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/users` | Listar todos los usuarios |
| `GET` | `/api/users/{id}` | Obtener usuario por ID |
| `PUT` | `/api/users/{id}` | Actualizar usuario |
| `DELETE` | `/api/users/{id}` | Desactivar usuario (soft-delete) |
## Propiedades
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/properties` | Listar propiedades activas |
| `GET` | `/api/properties/tree` | Obtener propiedades raíz (sin padre) |
| `GET` | `/api/properties/{id}/children` | Obtener hijos de una propiedad |
| `GET` | `/api/properties/{id}` | Obtener propiedad por ID |
| `POST` | `/api/properties` | Crear propiedad |
| `PUT` | `/api/properties/{id}` | Actualizar propiedad |
| `PATCH` | `/api/properties/{id}/status` | Cambiar estado de propiedad |
| `GET` | `/api/properties/{id}/history` | Historial de cambios de estado |
| `DELETE` | `/api/properties/{id}` | Eliminar propiedad (soft-delete) |
| `GET` | `/api/properties/types` | Listar tipos de propiedad |
| `GET` | `/api/properties/statuses` | Listar estados de propiedad |
**Estructura de Property:**
```json
{
"id": 1,
"parent": null,
"group": { "id": 1, "name": "Residencial Centro" },
"type": { "id": 2, "name": "PISO", "description": "Vivienda..." },
"status": { "id": 2, "name": "ALQUILADO", "description": "..." },
"reference": "PIS-001",
"name": "Piso Centro",
"addressStreet": "Calle Mayor",
"addressNumber": "12",
"addressCity": "Madrid",
"addressPostalCode": "28001",
"addressProvince": "Madrid",
"cadastralRef": "1234567VK1234A",
"surfaceM2": 85.50,
"floor": "3",
"door": "A",
"rentalAmount": 850.00,
"active": true
}
```
## Conjuntos (Property Groups)
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/property-groups` | Listar todos los conjuntos |
| `GET` | `/api/property-groups/{id}` | Obtener conjunto por ID |
| `POST` | `/api/property-groups` | Crear conjunto |
| `PUT` | `/api/property-groups/{id}` | Actualizar conjunto |
| `DELETE` | `/api/property-groups/{id}` | Eliminar conjunto |
| `GET` | `/api/property-groups/{id}/properties` | Propiedades pertenecientes al conjunto |
**Estructura de PropertyGroup:**
```json
{
"id": 1,
"name": "Residencial Centro",
"addressStreet": "Calle Mayor",
"addressNumber": "10",
"addressCity": "Madrid",
"addressPostalCode": "28001",
"isActive": true
}
```
## Inquilinos
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/tenants?search=` | Listar/buscar inquilinos |
| `GET` | `/api/tenants/{id}` | Obtener inquilino por ID |
| `POST` | `/api/tenants` | Crear inquilino |
| `PUT` | `/api/tenants/{id}` | Actualizar inquilino |
| `DELETE` | `/api/tenants/{id}` | Eliminar inquilino (soft-delete) |
## Contratos
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/contracts?propertyId=&tenantId=` | Listar/filtrar contratos |
| `GET` | `/api/contracts/{id}` | Obtener contrato por ID |
| `POST` | `/api/contracts` | Crear contrato (auto: propiedad → ALQUILADO) |
| `PUT` | `/api/contracts/{id}` | Actualizar contrato |
| `POST` | `/api/contracts/{id}/terminate` | Terminar contrato (auto: propiedad → VACIO) |
## Recibos de Ingresos (Income Receipts)
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/income-receipts?propertyId=&contractId=&from=&to=&statusId=` | Listar/filtrar recibos de ingresos |
| `GET` | `/api/income-receipts/pending` | Total pendiente de cobro |
| `GET` | `/api/income-receipts/{id}` | Obtener recibo por ID |
| `POST` | `/api/income-receipts` | Crear recibo |
| `PUT` | `/api/income-receipts/{id}` | Actualizar recibo |
| `PATCH` | `/api/income-receipts/{id}/pay` | Registrar pago |
| `DELETE` | `/api/income-receipts/{id}` | Eliminar recibo |
## Plantillas de Gastos (Expense Templates)
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/expense-templates?propertyId=&categoryId=` | Listar/filtrar plantillas |
| `GET` | `/api/expense-templates/active` | Plantillas activas |
| `GET` | `/api/expense-templates/{id}` | Obtener plantilla por ID |
| `POST` | `/api/expense-templates` | Crear plantilla |
| `PUT` | `/api/expense-templates/{id}` | Actualizar plantilla |
| `PATCH` | `/api/expense-templates/{id}/toggle` | Activar/desactivar plantilla |
| `POST` | `/api/expense-templates/{id}/generate` | Generar recibo de gasto desde plantilla |
| `DELETE` | `/api/expense-templates/{id}` | Eliminar plantilla |
## Recibos de Gastos (Expense Receipts)
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/expense-receipts?propertyId=&templateId=&from=&to=&statusId=` | Listar/filtrar recibos de gastos |
| `GET` | `/api/expense-receipts/{id}` | Obtener recibo por ID |
| `POST` | `/api/expense-receipts` | Crear recibo manual |
| `PUT` | `/api/expense-receipts/{id}` | Actualizar recibo |
| `PATCH` | `/api/expense-receipts/{id}/pay` | Registrar pago |
| `DELETE` | `/api/expense-receipts/{id}` | Eliminar recibo |
## Incidencias
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/incidents?propertyId=&statusId=` | Listar/filtrar incidencias |
| `GET` | `/api/incidents/{id}` | Obtener incidencia por ID |
| `POST` | `/api/incidents` | Crear incidencia |
| `PATCH` | `/api/incidents/{id}/status` | Actualizar estado |
| `PATCH` | `/api/incidents/{id}/assign` | Asignar técnico |
| `PATCH` | `/api/incidents/{id}/schedule` | Programar reparación |
| `DELETE` | `/api/incidents/{id}` | Eliminar incidencia |
**Flujo de estados de incidencia:**
`SIN_REVISAR``TECNICO_AVISADO``REPARACION_PREVISTA``REPARADO`
`IGNORADO`
`ANULADO`
## Mantenimiento Programado
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/maintenance?propertyId=` | Listar mantenimientos |
| `GET` | `/api/maintenance/pending` | Mantenimientos pendientes |
| `GET` | `/api/maintenance/upcoming?from=&to=` | Próximos mantenimientos |
| `GET` | `/api/maintenance/{id}` | Obtener por ID |
| `POST` | `/api/maintenance` | Crear mantenimiento |
| `PUT` | `/api/maintenance/{id}` | Actualizar |
| `PATCH` | `/api/maintenance/{id}/complete` | Marcar completado |
| `DELETE` | `/api/maintenance/{id}` | Eliminar |
## Recibos
| Método | Ruta | Descripción | Rol |
|--------|------|-------------|-----|
| `GET` | `/api/receipts` | Listar recibos | ADMIN, GERENTE, CONTABLE |
| `GET` | `/api/receipts/{id}` | Obtener recibo | ADMIN, GERENTE, CONTABLE |
| `POST` | `/api/receipts/generate` | Generar recibo individual | ADMIN, GERENTE, CONTABLE |
| `POST` | `/api/receipts/generate-monthly` | Generar recibos mensuales | ADMIN |
| `GET` | `/api/receipts/{id}/pdf` | Descargar PDF | ADMIN, GERENTE, CONTABLE |
| `POST` | `/api/receipts/{id}/send-email` | Enviar por email | ADMIN, GERENTE, CONTABLE |
| `GET` | `/api/receipts/reports/monthly?year=&month=` | Reporte Excel mensual | ADMIN, GERENTE, CONTABLE |
### `POST /api/receipts/generate`
Genera un recibo individual para un contrato específico.
**Request body:**
```json
{
"contractId": 1,
"issueDate": "2026-07-01",
"dueDate": "2026-07-15",
"description": "Alquiler julio 2026"
}
```
### `POST /api/receipts/generate-monthly`
Genera recibos para todos los contratos activos cuyo `payment_day` coincida con el mes actual. Solo ADMIN.
### `GET /api/receipts/{id}/pdf`
Devuelve el PDF del recibo como `application/pdf` con header `Content-Disposition: attachment; filename="recibo-R-2026-00001.pdf"`.
### `POST /api/receipts/{id}/send-email`
Envía el recibo por email al inquilino con el PDF adjunto.
### `GET /api/receipts/reports/monthly?year=2026&month=7`
Descarga un informe Excel (`.xlsx`) con el resumen de recibos de ingresos, recibos de gastos y balance del mes.
## Notificaciones
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/notifications?unreadOnly=true` | Listar notificaciones del usuario |
| `GET` | `/api/notifications/unread-count` | Contar no leídas |
| `PATCH` | `/api/notifications/{id}/read` | Marcar como leída |
| `PATCH` | `/api/notifications/read-all` | Marcar todas como leídas |
## Dashboard
| Método | Ruta | Descripción |
|--------|------|-------------|
| `GET` | `/api/dashboard/summary` | Resumen general (contadores, YTD) |
| `GET` | `/api/dashboard/income-expense?year=2026` | Ingresos/gastos mensuales del año |
**Respuesta de `/summary`:**
```json
{
"success": true,
"data": {
"totalProperties": 10,
"rentedProperties": 5,
"activeContracts": 5,
"totalTenants": 8,
"openIncidents": 2,
"pendingMaintenance": 1,
"incomeYtd": 42500.00,
"expenseYtd": 12300.00,
"pendingIncome": 2850.00
}
}
```
## Documentos
| Método | Ruta | Descripción |
|--------|------|-------------|
| `POST` | `/api/documents/upload` | Subir archivo (multipart) |
| `GET` | `/api/documents/entity/{entityType}/{entityId}` | Documentos de una entidad |
| `GET` | `/api/documents/types-for-entity/{entityType}` | Tipos de documento permitidos para una entidad |
| `GET` | `/api/documents/search` | Búsqueda avanzada con filtros |
| `GET` | `/api/documents/{id}/download` | Descargar documento |
| `DELETE` | `/api/documents/{id}` | Eliminar documento |
| `GET` | `/api/documents/{id}/entities` | Ver entidades asociadas a un documento |
| `POST` | `/api/documents/{id}/entities` | Asociar documento a otra entidad |
| `DELETE` | `/api/documents/{id}/entities/{entityType}/{entityId}` | Desasociar documento de una entidad |
**Tipos de entidad soportados:** `PROPERTY`, `CONTRACT`, `TENANT`, `INCIDENT`, `MAINTENANCE`
**Upload params:** `file` (multipart), `entityType`, `entityId`, `documentTypeId`, `description` (opcional)
### `GET /api/documents/types-for-entity/{entityType}`
Devuelve los tipos de documento permitidos para una entidad, indicando cuáles son obligatorios.
**Response (200):**
```json
{
"success": true,
"data": [
{
"documentTypeId": 1,
"documentTypeName": "CONTRATO",
"canUpload": true,
"mustHave": true,
"description": "Documento principal del contrato de alquiler"
},
{
"documentTypeId": 10,
"documentTypeName": "OTRO",
"canUpload": true,
"mustHave": false,
"description": "Otros documentos del contrato"
}
]
}
```
## Códigos de Error
| Código | Significado |
|--------|-------------|
| 200 | OK |
| 201 | Creado |
| 400 | Bad Request (validación, datos incorrectos) |
| 401 | No autenticado |
| 403 | No autorizado (rol insuficiente) |
| 404 | Recurso no encontrado |
| 409 | Conflicto (duplicado) |
| 500 | Error interno del servidor |
## Documentación Interactiva (Swagger)
Disponible en `http://localhost:8080/swagger-ui.html` cuando el backend está corriendo. También se puede obtener el spec OpenAPI en `http://localhost:8080/api-docs`.
+155
View File
@@ -0,0 +1,155 @@
# Arquitectura del Sistema
## 1. Visión General
Sa Polar es un **monolito modular** con frontend separado. El backend Spring Boot expone una API RESTful que consume un frontend React. La base de datos MySQL se inicializa mediante un script SQL ejecutado en el arranque del contenedor.
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Cliente │────▶│ Frontend │────▶│ Backend │────▶│ MySQL 8 │
│ (Browser) │ │ (React 19) │ │ (Spring Boot)│ │ │
│ │◀────│ (Vite 8) │◀────│ (Java 21) │◀────│ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
│ /api/* │ JPA/Hibernate
▼ ▼
Nginx (proxy) JWT Security
```
## 2. Componentes
### 2.1 Backend (Spring Boot 3.4.1)
El backend se organiza en **paquetes verticales** por dominio de negocio:
| Paquete | Responsabilidad |
|---------|----------------|
| `auth` | Autenticación JWT, login, registro, refresh |
| `user` | CRUD de usuarios, roles, permisos |
| `property` | Gestión de inmuebles (jerárquica), tipos, estados y conjuntos (PropertyGroup) |
| `tenant` | Gestión de inquilinos (personas físicas/jurídicas) |
| `contract` | Contratos de alquiler, estados, periodos de pago |
| `finance.income` | Recibos de ingresos (IncomeReceipt), cobros, categorías |
| `finance.expense` | Plantillas de gastos (ExpenseTemplate), recibos de gastos (ExpenseReceipt), categorías |
| `finance.receipt` | Recibos, PDF, email, reportes Excel, scheduler |
| `incident` | Incidencias, prioridades, asignación técnica |
| `maintenance` | Mantenimiento programado recurrente |
| `notification` | Notificaciones por usuario |
| `document` | Gestión de documentos adjuntos (polimórfico) |
| `dashboard` | Agregaciones y resúmenes |
| `config` | Seguridad, CORS, OpenAPI, almacenamiento |
| `common` | DTOs genéricos, excepciones, utilidades |
### 2.2 Frontend (React 19 + TypeScript 6)
Aplicación SPA con las siguientes capas:
| Capa | Descripción |
|------|-------------|
| `api/client.ts` | Instancia Axios con interceptor JWT y redirección 401 |
| `api/auth.ts` | Funciones de login y refresh |
| `api/resources.ts` | Funciones CRUD para cada recurso |
| `contexts/AuthContext.tsx` | Estado global de autenticación |
| `components/Layout.tsx` | Sidebar de navegación + contenido principal |
| `pages/*.tsx` | Páginas individuales (Login, Dashboard, Properties, etc.) |
| `types/api.ts` | Interfaces TypeScript para los DTOs |
### 2.3 Base de datos (MySQL 8)
Esquema gestionado mediante script SQL de inicialización (`db/init.sql`). Hibernate opera en modo `validate` para verificar que el mapeo JPA coincida con el esquema existente.
## 3. Seguridad
### 3.1 Autenticación JWT
1. El usuario envía credenciales a `POST /api/auth/login`
2. El servidor valida contra la base de datos y devuelve:
- `accessToken`: válido por 24 horas
- `refreshToken`: válido por 30 días
3. El frontend almacena los tokens en `localStorage`
4. Cada petición incluye `Authorization: Bearer <token>`
5. El `JwtAuthenticationFilter` extrae y valida el token en cada request
6. Si el token expira, el frontend usa `POST /api/auth/refresh` para obtener uno nuevo
### 3.2 Roles y permisos
| Rol | Acceso |
|-----|--------|
| `ADMIN` | Todos los endpoints, incluyendo gestión de usuarios |
| `GERENTE` | Propiedades, inquilinos, contratos, incidencias, mantenimiento, recibos de ingresos, plantillas de gastos, recibos de gastos, dashboard |
| `CONTABLE` | Recibos de ingresos, plantillas de gastos, recibos de gastos, dashboard, reportes |
| `VISUALIZADOR` | Autenticado (acceso básico de solo lectura según configuración) |
### 3.3 Seguridad adicional
- CSRF deshabilitado (API stateless)
- Sesiones sin estado (`SessionCreationPolicy.STATELESS`)
- CORS configurable mediante `app.cors.allowed-origins`
- Contraseñas almacenadas con BCrypt
## 4. Flujo de Datos
### 4.1 Autenticación
```
Browser Frontend Backend MySQL
│ │ │ │
│ login(user, pass) │ │ │
│──────────────────────▶│ POST /api/auth/login │ │
│ │───────────────────────▶│ │
│ │ │ SELECT user by email │
│ │ │───────────────────────▶│
│ │ │◀───────────────────────│
│ │ │ Verificar BCrypt hash │
│ │ │ Generar JWT tokens │
│ │◀───────────────────────│ │
│◀──────────────────────│ TokenResponse │ │
│ Guardar en localStorage │ │
```
### 4.2 Generación de recibos automáticos
```
Scheduler (cron: 0 0 6 1 * ?)
ReceiptService.generateMonthlyReceipts()
├── Buscar contratos ACTIVOS con payment_day = mes actual
├── Para cada contrato:
│ ├── Obtener siguiente número de serie (ReceiptSeries)
│ ├── Crear registro IncomeReceipt con receipt_number
│ ├── Generar PDF (PdfReceiptService)
│ └── Enviar email si el inquilino tiene email (EmailReceiptService)
└── Log de emails enviados (email_log)
```
## 5. Despliegue
### 5.1 Docker Compose
Tres servicios orquestados:
1. **mysql**: Imagen `mysql:8.0`, puerto `3307:3306`, volumen persistente, script init.sql
2. **backend**: Build multi-etapa (Maven + JRE), puerto `8080:8080`
3. **frontend**: Build multi-etapa (Node + Nginx), puerto `3000:3000`, proxy reverso `/api/` al backend
### 5.2 Entornos
| Entorno | Backend URL | Frontend URL | Propósito |
|---------|-------------|--------------|-----------|
| Desarrollo | `localhost:8080` | `localhost:5173` (Vite) | Desarrollo local |
| Producción | `localhost:8080` | `localhost:3000` (Nginx) | Docker compose |
## 6. Dependencias Externas
| Dependencia | Versión | Uso |
|-------------|---------|-----|
| Spring Boot | 3.4.1 | Framework principal |
| JJWT | 0.12.6 | Tokens JWT |
| SpringDoc OpenAPI | 2.7.0 | Documentación Swagger |
| iText | 8.0.5 | Generación de PDFs |
| Apache POI | 5.3.0 | Generación de Excel |
| MapStruct | 1.6.3 | Mapeo de DTOs (si se usa) |
| Lombok | 1.18.36 | Reducción de boilerplate |
| Flyway | - | Dependencia incluida pero deshabilitada |
+569
View File
@@ -0,0 +1,569 @@
# Esquema de Base de Datos
## Visión General
Base de datos MySQL 8 con 24 tablas. El esquema se inicializa mediante `db/init.sql` en el arranque del contenedor MySQL. Hibernate opera en modo `validate` para verificar que el mapeo JPA coincida con el esquema.
## Diagrama de Tablas
```
property_groups ──── properties ──── property_types
│ │ property_statuses
│ └── property_status_history
roles ──── users ──── user_permissions
├── tenants ──── tenant_types
├── contracts ──── contract_statuses
│ │ payment_periods
│ │
│ └── income_receipts ──── income_categories
│ │ income_statuses
│ │
│ └── email_log
├── expense_templates ──── expense_categories
│ expense_statuses
│ payment_periods
├── expense_receipts ──── expense_categories
│ expense_statuses
├── incidents ──── incident_statuses
│ incident_priorities
├── scheduled_maintenance ──── maintenance_periods
└── notifications ──── notification_types
documents ──── document_types ──── document_type_entity_allowed
receipt_series
```
## Tablas Detalladas
### 1. roles
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | Nombre del rol |
| description | VARCHAR(255) | Descripción |
**Datos semilla:** ADMIN, GERENTE, CONTABLE, VISUALIZADOR
### 2. users
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| username | VARCHAR(50) UNIQUE | Nombre de usuario |
| email | VARCHAR(100) UNIQUE | Correo electrónico |
| password_hash | VARCHAR(255) | Hash BCrypt |
| full_name | VARCHAR(150) | Nombre completo |
| phone | VARCHAR(20) | Teléfono |
| role_id | INT FK → roles(id) | Rol del usuario |
| active | BOOLEAN | Usuario activo (soporta soft-delete) |
| created_at | DATETIME | Fecha de creación |
| updated_at | DATETIME | Fecha de modificación |
| last_login | DATETIME | Último inicio de sesión |
### 3. user_permissions
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| user_id | BIGINT FK → users(id) CASCADE | Usuario |
| permission | VARCHAR(50) | Permiso específico |
| granted | BOOLEAN | Concedido/denegado |
**Unique:** (user_id, permission)
### 4. property_types
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(50) UNIQUE | Tipo (EDIFICIO, PISO, etc.) |
| description | VARCHAR(255) | Descripción |
### 5. property_statuses
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(50) UNIQUE | Estado (DISPONIBLE, ALQUILADO, etc.) |
| description | VARCHAR(255) | Descripción |
### 6. properties
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| parent_id | BIGINT FK → properties(id) SET NULL | Propiedad padre (jerarquía) |
| group_id | BIGINT FK → property_groups(id) SET NULL | Conjunto al que pertenece |
| type_id | INT FK → property_types(id) | Tipo de propiedad |
| status_id | INT FK → property_statuses(id) | Estado actual |
| reference | VARCHAR(50) UNIQUE | Referencia interna |
| name | VARCHAR(200) | Nombre identificativo |
| description | TEXT | Descripción |
| address_street | VARCHAR(200) | Calle |
| address_number | VARCHAR(20) | Número |
| address_city | VARCHAR(100) | Ciudad |
| address_postal_code | VARCHAR(10) | Código postal |
| address_province | VARCHAR(100) | Provincia |
| cadastral_ref | VARCHAR(30) | Referencia catastral |
| surface_m2 | DECIMAL(10,2) | Superficie en m² |
| floor | VARCHAR(50) | Planta / Piso |
| door | VARCHAR(50) | Puerta |
| rental_amount | DECIMAL(12,2) | Importe de alquiler |
| rented_since | DATE | Alquilado desde |
| vacant_since | DATE | Vacío desde |
| occupied_since | DATE | Ocupado desde |
| notes | TEXT | Notas |
| active | BOOLEAN | Activo (soft-delete) |
| created_by | BIGINT FK → users(id) SET NULL | Creado por |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
**Índices:** parent, type, status, active
### 7. property_status_history
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| property_id | BIGINT FK → properties(id) CASCADE | Propiedad |
| status_id | INT FK → property_statuses(id) | Nuevo estado |
| changed_by | BIGINT FK → users(id) SET NULL | Quién cambió |
| changed_at | DATETIME | Cuándo |
| notes | VARCHAR(500) | Motivo del cambio |
### 8. property_groups
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| name | VARCHAR(200) | Nombre del conjunto |
| address_street | VARCHAR(200) | Calle |
| address_number | VARCHAR(20) | Número |
| address_city | VARCHAR(100) | Ciudad |
| address_postal_code | VARCHAR(10) | Código postal |
| is_active | BOOLEAN | Activo |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
**Relaciones:** Una propiedad puede pertenecer opcionalmente a un conjunto. Al eliminar un conjunto, las propiedades asociadas quedan con `group_id = NULL` (`ON DELETE SET NULL`).
### 9. tenant_types
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | PERSONA_FISICA / PERSONA_JURIDICA |
### 10. tenants
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| tenant_type_id | INT FK → tenant_types(id) | Tipo de inquilino |
| fiscal_id | VARCHAR(20) UNIQUE | DNI/NIF/CIF |
| full_name | VARCHAR(200) | Nombre o razón social |
| business_name | VARCHAR(200) | Solo personas jurídicas |
| email | VARCHAR(100) | Correo electrónico |
| phone | VARCHAR(20) | Teléfono |
| address | VARCHAR(300) | Dirección |
| iban | VARCHAR(34) | IBAN para domiciliación |
| notes | TEXT | Notas |
| active | BOOLEAN | Activo (soft-delete) |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
### 11. contract_statuses
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | ACTIVO, VENCIDO, RENOVADO, RESCINDIDO, ANULADO |
### 12. payment_periods
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | MENSUAL, TRIMESTRAL, SEMESTRAL, ANUAL |
### 13. contracts
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| property_id | BIGINT FK → properties(id) | Propiedad |
| tenant_id | BIGINT FK → tenants(id) | Inquilino |
| status_id | INT FK → contract_statuses(id) | Estado |
| period_id | INT FK → payment_periods(id) DEFAULT 1 | Período de pago |
| contract_number | VARCHAR(50) UNIQUE | Número de contrato |
| start_date | DATE | Fecha de inicio |
| end_date | DATE | Fecha de fin |
| renewal_date | DATE | Fecha de renovación |
| rental_amount | DECIMAL(12,2) | Renta mensual |
| deposit_amount | DECIMAL(12,2) | Fianza |
| payment_day | INT DEFAULT 1 | Día de pago |
| payment_day_end | INT DEFAULT NULL | Día final del rango de pago (NULL = día único) |
| iban_charge | VARCHAR(34) | IBAN domiciliación |
| notes | TEXT | Notas |
| signed_at | DATE | Fecha de firma |
| terminated_at | DATE | Fecha de terminación |
| termination_cause | VARCHAR(500) | Causa de terminación |
| created_by | BIGINT FK → users(id) SET NULL | Creado por |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
### 14. document_types
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(50) UNIQUE | Tipo de documento |
**Tipos:** CONTRATO, ANEXO_CONTRATO, DNI_ARREENDATARIO, CIF_EMPRESA, FOTO_PROPIEDAD, FOTO_INCIDENCIA, FACTURA, JUSTIFICANTE_PAGO, CERTIFICADO, OTRO
### 16. documents
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| document_type_id | INT FK → document_types(id) | Tipo |
| original_name | VARCHAR(255) | Nombre original del archivo |
| stored_name | VARCHAR(255) | Nombre almacenado en disco (UUID + extensión) |
| mime_type | VARCHAR(100) | Tipo MIME |
| file_size | BIGINT | Tamaño en bytes |
| description | VARCHAR(500) | Descripción |
| uploaded_by | BIGINT FK → users(id) SET NULL | Subido por |
| uploaded_at | DATETIME | Fecha de subida |
**Relaciones:** Relación One-to-Many con `document_entities`.
### 17. document_entities (tabla pivote)
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| document_id | BIGINT FK → documents(id) CASCADE | Documento |
| entity_type | VARCHAR(30) | Tipo de entidad asociada |
| entity_id | BIGINT | ID de la entidad |
| created_at | DATETIME | Fecha de creación |
**Entidades soportadas:** PROPERTY, TENANT, CONTRACT, INCOME, EXPENSE, INCIDENT, MAINTENANCE
**Caso de uso:** Permite asociar un mismo documento a múltiples entidades. Por ejemplo, una fianza puede estar asociada tanto a un INCOME (cuando se recibe) como a un EXPENSE (cuando se devuelve).
**Índices:** UNIQUE(document_id, entity_type, entity_id), INDEX(entity_type, entity_id)
### 17. document_type_entity_allowed (tabla pivote)
Define qué tipos de documento pueden asociarse a qué tipos de entidades del sistema.
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| document_type_id | INT FK → document_types(id) | Tipo de documento |
| entity_type | VARCHAR(30) | Tipo de entidad (PROPERTY, TENANT, CONTRACT, INCOME, EXPENSE, INCIDENT, MAINTENANCE) |
| can_upload | BOOLEAN | Si el usuario puede subir este tipo en esta entidad |
| must_have | BOOLEAN | Si es obligatorio al crear/editar la entidad |
| description | VARCHAR(255) | Descripción del uso del documento |
| created_at | DATETIME | Fecha de creación |
**Relaciones permitidas:**
| Tipo Documento | Entidad | Obligatorio | Descripción |
|----------------|---------|-------------|-------------|
| CONTRATO | CONTRACT | Sí | Documento principal del contrato |
| CONTRATO | TENANT | No | Copia firmada por inquilino |
| CONTRATO | PROPERTY | No | Contrato asociado al inmueble |
| ANEXO_CONTRATO | CONTRACT | No | Anexos y modificaciones |
| DNI_ARRENDATARIO | TENANT | Sí | DNI del inquilino |
| CIF_EMPRESA | TENANT | No | Para personas jurídicas |
| FOTO_PROPIEDAD | PROPERTY | Sí | Fotos del inmueble |
| FOTO_INCIDENCIA | INCIDENT | No | Fotos de la incidencia |
| FACTURA | EXPENSE | Sí | Factura o justificante del gasto |
| JUSTIFICANTE_PAGO | INCOME | Sí | Justificante de pago |
| CERTIFICADO | TENANT | No | Certificados varios |
| CERTIFICADO | CONTRACT | No | Certificados asociados |
| OTRO | Todas | No | Otros documentos |
**Índices:** UNIQUE(document_type_id, entity_type), INDEX(entity_type)
### 18. income_categories
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| name | VARCHAR(100) | Nombre |
| description | VARCHAR(255) | Descripción |
| active | BOOLEAN | Activo |
**Categorías:** ALQUILER, FIANZA, GASTOS_COMUNIDAD, INTERESES_DEMORA, INDEMNIZACION, OTROS_INGRESOS
### 17. income_statuses
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | PENDIENTE, PAGADO, VENCIDO, PARCIAL, ANULADO |
### 18. income_receipts
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| contract_id | BIGINT FK → contracts(id) SET NULL | Contrato asociado |
| property_id | BIGINT FK → properties(id) | Propiedad (NOT NULL) |
| tenant_id | BIGINT FK → tenants(id) SET NULL | Inquilino |
| bank_account_id | BIGINT FK → bank_accounts(id) SET NULL | Cuenta bancaria |
| is_domiciled | BOOLEAN DEFAULT FALSE | Domiciliado |
| category_id | BIGINT FK → income_categories(id) SET NULL | Categoría |
| status_id | INT FK → income_statuses(id) | Estado |
| period_label | VARCHAR(20) | Etiqueta de período (ej: "2026-07") |
| amount | DECIMAL(12,2) | Importe bruto |
| tax_withheld | DECIMAL(12,2) DEFAULT 0 | Retención IRPF |
| net_amount | DECIMAL(12,2) | Importe neto |
| issue_date | DATE | Fecha de emisión |
| due_date | DATE | Fecha de vencimiento |
| payment_date | DATE | Fecha de pago |
| payment_method | VARCHAR(30) | TRANSFERENCIA / EFECTIVO / BIZUM / RECIBO / TARJETA |
| description | VARCHAR(500) | Descripción |
| receipt_number | VARCHAR(50) | Número de recibo |
| notes | TEXT | Notas |
| created_by | BIGINT FK → users(id) SET NULL | Creado por |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
### 19. expense_categories
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| name | VARCHAR(100) | Nombre |
| description | VARCHAR(255) | Descripción |
| active | BOOLEAN | Activo |
### 20. expense_statuses
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | PENDIENTE, PAGADO, VENCIDO, ANULADO |
### 21. expense_templates
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| property_id | BIGINT FK → properties(id) NULL | Propiedad asociada |
| property_group_id | BIGINT FK → property_groups(id) NULL | Conjunto asociado |
| bank_account_id | BIGINT FK → bank_accounts(id) NULL | Cuenta bancaria |
| is_domiciled | BOOLEAN DEFAULT FALSE | Domiciliado |
| category_id | BIGINT FK → expense_categories(id) SET NULL | Categoría |
| period_id | INT FK → payment_periods(id) | Periodicidad |
| payment_day | INT DEFAULT 1 | Día de pago |
| supplier_name | VARCHAR(200) | Proveedor |
| supplier_fiscal_id | VARCHAR(20) | NIF/CIF |
| amount | DECIMAL(12,2) NULL | Importe fijo (NULL = variable) |
| tax_amount | DECIMAL(12,2) | Importe impuestos |
| description | VARCHAR(500) | Concepto |
| notes | TEXT | Notas |
| is_variable | BOOLEAN DEFAULT FALSE | Importe variable (usuario rellena cada mes) |
| active | BOOLEAN DEFAULT TRUE | Template activo/inactivo |
| created_by | BIGINT FK → users(id) SET NULL | Creador |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
### 22. expense_receipts
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| template_id | BIGINT FK → expense_templates(id) SET NULL | Template origen (NULL = gasto manual) |
| property_id | BIGINT FK → properties(id) NULL | Propiedad asociada |
| property_group_id | BIGINT FK → property_groups(id) NULL | Conjunto asociado |
| bank_account_id | BIGINT FK → bank_accounts(id) NULL | Cuenta bancaria |
| is_domiciled | BOOLEAN DEFAULT FALSE | Domiciliado |
| category_id | BIGINT FK → expense_categories(id) SET NULL | Categoría |
| status_id | INT FK → expense_statuses(id) | Estado |
| supplier_name | VARCHAR(200) | Proveedor |
| supplier_fiscal_id | VARCHAR(20) | NIF/CIF |
| invoice_number | VARCHAR(50) | Número de factura |
| amount | DECIMAL(12,2) DEFAULT 0 | Base imponible |
| tax_amount | DECIMAL(12,2) DEFAULT 0 | Importe impuestos |
| total_amount | DECIMAL(12,2) | Total con impuestos |
| is_variable | BOOLEAN DEFAULT FALSE | Es gasto variable |
| previous_amount | DECIMAL(12,2) NULL | Importe del periodo anterior (para variables) |
| issue_date | DATE | Fecha de emisión |
| due_date | DATE | Fecha de vencimiento |
| payment_date | DATE | Fecha de pago |
| payment_method | VARCHAR(30) | Método de pago |
| description | VARCHAR(500) | Concepto |
| notes | TEXT | Notas |
| created_by | BIGINT FK → users(id) SET NULL | Creador |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
**Índices:** template, property, property_group, category, status, issue_date
### 23. incident_statuses
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(50) UNIQUE | SIN_REVISAR, TECNICO_AVISADO, REPARACION_PREVISTA, REPARADO, IGNORADO, ANULADO |
### 24. incident_priorities
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(20) UNIQUE | BAJA, MEDIA, ALTA, URGENTE |
### 25. incidents
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| property_id | BIGINT FK → properties(id) | Propiedad |
| status_id | INT FK → incident_statuses(id) | Estado |
| priority_id | INT FK → incident_priorities(id) | Prioridad |
| title | VARCHAR(200) | Título |
| description | TEXT | Descripción |
| reported_by | BIGINT FK → users(id) SET NULL | Reportado por |
| assigned_to | BIGINT FK → users(id) SET NULL | Asignado a |
| reported_at | DATETIME | Fecha de reporte |
| scheduled_date | DATE | Fecha prevista reparación |
| resolved_at | DATETIME | Fecha de resolución |
| resolution_notes | TEXT | Notas de resolución |
| cost_estimate | DECIMAL(12,2) | Coste estimado |
| final_cost | DECIMAL(12,2) | Coste final |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
### 26. maintenance_periods
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(30) UNIQUE | UNICA_VEZ, MENSUAL, TRIMESTRAL, etc. |
### 27. scheduled_maintenance
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| property_id | BIGINT FK → properties(id) | Propiedad |
| period_id | INT FK → maintenance_periods(id) | Periodicidad |
| title | VARCHAR(200) | Título |
| description | TEXT | Descripción |
| estimated_cost | DECIMAL(12,2) | Coste estimado |
| last_execution | DATE | Última ejecución |
| next_execution | DATE | Próxima ejecución |
| reminder_days_before | INT DEFAULT 30 | Días antes para recordatorio |
| responsible | VARCHAR(200) | Responsable |
| notes | TEXT | Notas |
| completed | BOOLEAN | Completado |
| completed_at | DATE | Fecha de finalización |
| completed_by | BIGINT FK → users(id) SET NULL | Completado por |
| created_by | BIGINT FK → users(id) SET NULL | Creado por |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
### 28. notification_types
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | INT PK | Auto-increment |
| name | VARCHAR(50) UNIQUE | INCIDENCIA_ABIERTA, MANTENIMIENTO_PROXIMO, RECIBO_VENCIDO, CONTRATO_PROXIMO_VENCER, CONTRATO_VENCIDO, SISTEMA |
### 29. notifications
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| user_id | BIGINT FK → users(id) CASCADE | Usuario destinatario |
| type_id | INT FK → notification_types(id) | Tipo |
| title | VARCHAR(200) | Título |
| body | TEXT | Cuerpo |
| entity_type | VARCHAR(30) | Tipo de entidad relacionada |
| entity_id | BIGINT | ID de entidad |
| sent_by_email | BOOLEAN | Enviado por email |
| read | BOOLEAN | Leído |
| read_at | DATETIME | Fecha de lectura |
| created_at | DATETIME | Creación |
### 30. receipt_series
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| series_name | VARCHAR(50) | Nombre de la serie |
| fiscal_year | INT | Año fiscal |
| last_number | INT DEFAULT 0 | Último número usado |
| prefix | VARCHAR(20) DEFAULT 'R-' | Prefijo del número |
| active | BOOLEAN | Serie activa |
| created_at | DATETIME | Creación |
| updated_at | DATETIME | Modificación |
**Unique:** (series_name, fiscal_year)
### 31. email_log
| Columna | Tipo | Descripción |
|---------|------|-------------|
| id | BIGINT PK | Auto-increment |
| income_receipt_id | BIGINT | ID del recibo de ingreso asociado |
| recipient_email | VARCHAR(200) | Email del destinatario |
| subject | VARCHAR(300) | Asunto |
| body | TEXT | Cuerpo del mensaje |
| success | BOOLEAN | Envío exitoso |
| error_message | TEXT | Mensaje de error si falló |
| sent_at | DATETIME | Fecha de envío |
## Notas Técnicas
- **Soft-delete:** Las tablas `users`, `properties`, `tenants` tienen columna `active` para borrado lógico.
- **Índices:** Las columnas más consultadas tienen índices (foreign keys, fechas, estados).
- **Charset:** `utf8mb4` con collation `utf8mb4_unicode_ci` para soporte completo de Unicode.
- **Motor:** Todas las tablas usan InnoDB para integridad referencial y transacciones.
- **Integridad referencial:** 32 constraints FK en total (incluyendo V5 para document_type_entity_allowed).
- **Al borrar el volumen de datos**, el script `init.sql` se ejecuta automáticamente al arrancar el contenedor MySQL.
- **Hibernate:** Opera con `ddl-auto: validate`. Cualquier cambio en las entidades JPA requiere actualizar `init.sql`.
## Codificación de caracteres (UTF-8)
Para evitar problemas con acentos y caracteres especiales (tildes, eñes, etc.) en todo el sistema:
- **Base de datos:** charset `utf8mb4` y collation `utf8mb4_unicode_ci`.
- **Conexión JDBC (`application.yml`):** la URL incluye `characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci` para forzar la lectura/escritura en UTF-8.
- **Respuestas HTTP (`application.yml`):** `server.servlet.encoding.charset=UTF-8` y `force=true` para que el header `Content-Type` siempre incluya `charset=UTF-8`.
- **Frontend (nginx):** directiva `charset utf-8;` en el bloque `server` del `Dockerfile.frontend`.
- **Datos de prueba (`seed.sql`):** el script incluye `SET NAMES utf8mb4;` al inicio para evitar corrupciones al insertar desde clientes con charset por defecto (latin1).
Si se ejecuta `seed.sql` manualmente desde un cliente MySQL, usar siempre `--default-character-set=utf8mb4` o ejecutar primero `SET NAMES utf8mb4;`.
## Datos de prueba (seed)
El archivo `backend/src/main/resources/db/seed.sql` contiene datos de demostración (usuarios adicionales, propiedades, inquilinos, contratos, recibos de ingresos, plantillas de gastos, recibos de gastos, incidencias, etc.).
**Para poblar la base de datos tras un `docker compose down -v`:**
```bash
# Copiar el script al contenedor
docker cp backend/src/main/resources/db/seed.sql sa-polar-mysql:/tmp/seed.sql
# Ejecutarlo forzando UTF-8
docker exec sa-polar-mysql bash -c "mysql -u root -proot --default-character-set=utf8mb4 sa_polar < /tmp/seed.sql"
```
> Las contraseñas de los usuarios de prueba (`admin`, `gerente`, `contable`) son todas `admin123` y solo válidas para desarrollo local.
+382
View File
@@ -0,0 +1,382 @@
# Manual de Usuario - Sa Polar
## 1. Introducción
**Sa Polar** es un sistema de gestión de alquileres que permite administrar propiedades inmobiliarias, contratos de arrendamiento, inquilinos, ingresos, gastos, incidencias y mantenimiento programado. Está diseñado para propietarios, administradores de fincas y gestores de alquileres.
## 2. Acceso al Sistema
### 2.1 Inicio de sesión
1. Abrir el navegador y acceder a la URL del sistema:
- **Entorno Docker:** `http://localhost:3000`
- **Entorno desarrollo:** `http://localhost:5173`
2. Introducir credenciales:
- **Usuario:** `admin`
- **Contraseña:** `admin123`
3. Hacer clic en **Iniciar Sesión**
![Pantalla de login](images/login.png)
### 2.2 Roles de usuario
| Rol | Descripción |
|-----|-------------|
| **ADMIN** | Acceso completo a todas las funcionalidades del sistema |
| **GERENTE** | Gestión de propiedades, contratos, inquilinos, incidencias, mantenimiento, recibos y finanzas |
| **CONTABLE** | Gestión de ingresos, gastos, recibos y reportes |
| **VISUALIZADOR** | Acceso de solo lectura a la información |
## 3. Navegación
Una vez dentro del sistema, aparece un menú lateral con las siguientes secciones:
| Sección | Icono | Descripción |
|---------|-------|-------------|
| **Dashboard** | 📊 | Resumen general del sistema |
| **Propiedades** | 🏠 | Gestión de inmuebles |
| **Conjuntos** | 🏢 | Agrupación de propiedades (edificios, urbanizaciones) |
| **Inquilinos** | 👤 | Gestión de arrendatarios |
| **Contratos** | 📝 | Contratos de alquiler |
| **Recibos de Ingreso** | 💰 | Cobros y recibos (antes "Ingresos") |
| **Gastos** | 💸 | Pagos y facturas |
| **Incidencias** | 🔧 | Averías y reparaciones |
## 4. Dashboard
La pantalla principal muestra un resumen con:
- **Total de propiedades** registradas
- **Inquilinos** activos
- **Contratos activos** actualmente vigentes
- **Ingresos pendientes** de cobro
## 5. Gestión de Propiedades
### 5.1 Listado de propiedades
Muestra una tabla con todas las propiedades: ID, nombre, conjunto, ciudad, tipo, estado e importe de alquiler.
### 5.2 Jerarquía de propiedades
Las propiedades pueden organizarse jerárquicamente (ej: un edificio contiene varios pisos). La propiedad "padre" se selecciona al crear una nueva propiedad.
### 5.3 Tipos de propiedad
| Tipo | Descripción |
|------|-------------|
| EDIFICIO | Edificio completo con varias plantas |
| PISO | Vivienda en un edificio de pisos |
| LOCAL_COMERCIAL | Local comercial |
| BAR | Bar o restaurante |
| NAVE | Nave industrial o almacén |
| GARAJE | Plaza de garaje |
| TRASTERO | Trastero |
| OFICINA | Oficina o despacho |
| ADOSADO | Vivienda unifamiliar adosada |
| CHALET | Vivienda unifamiliar independiente |
### 5.4 Estados de propiedad
| Estado | Descripción |
|--------|-------------|
| DISPONIBLE | Disponible para alquilar |
| ALQUILADO | Actualmente alquilado |
| VACIO | Vacío, sin inquilino |
| ANUNCIADO | Anunciado para alquiler |
| MANTENIMIENTO | En obras o mantenimiento |
Los estados se actualizan automáticamente al crear o terminar contratos.
### 5.5 Historial de estados
Cada cambio de estado de una propiedad queda registrado con fecha, usuario y motivo.
### 5.6 Conjuntos (Agrupaciones)
Los conjuntos permiten agrupar propiedades que comparten una misma ubicación o promoción (edificios, urbanizaciones, residenciales).
#### 5.6.1 Listado de conjuntos
Muestra todos los conjuntos con ID, nombre y dirección.
#### 5.6.2 Crear un conjunto
1. Ir a **Conjuntos** en el menú lateral
2. Hacer clic en **+ Nuevo Conjunto**
3. Introducir nombre y dirección
4. Hacer clic en **Crear**
#### 5.6.3 Asignar propiedades a un conjunto
Al editar una propiedad, el campo **Conjunto** permite seleccionar el grupo al que pertenece. Una propiedad puede pertenecer a un solo conjunto o a ninguno.
#### 5.6.4 Detalle del conjunto
Al hacer clic en "Ver" sobre un conjunto, se muestran sus datos junto con una tabla de las propiedades que pertenecen a ese conjunto.
## 6. Gestión de Inquilinos
### 6.1 Listado de inquilinos
Muestra todos los inquilinos con nombre, NIF/CIF, email, teléfono y tipo (persona física o jurídica).
### 6.2 Tipos de inquilino
- **PERSONA_FISICA:** Arrendatario individual (DNI)
- **PERSONA_JURIDICA:** Empresa o entidad (CIF)
### 6.3 Datos del inquilino
- Nombre completo (o razón social)
- NIF/CIF
- Dirección
- Teléfono y email
- IBAN para domiciliación de pagos
## 7. Gestión de Contratos
### 7.1 Listado de contratos
Muestra: número de contrato, propiedad, inquilino, importe, fechas de inicio/fin y estado.
### 7.2 Estados de contrato
| Estado | Descripción |
|--------|-------------|
| ACTIVO | Contrato vigente |
| VENCIDO | Fecha de fin superada |
| RENOVADO | Renovado a un nuevo contrato |
| RESCINDIDO | Cancelado antes del fin |
| ANULADO | Anulado sin efecto |
### 7.3 Creación de contrato
Al crear un contrato:
1. Seleccionar propiedad (debe estar en estado DISPONIBLE)
2. Seleccionar inquilino
3. Introducir importe de renta, fecha de inicio, período de pago
4. Opcional: fianza, fecha de fin, día de pago, IBAN domiciliación
5. **Automáticamente:** la propiedad pasa a estado ALQUILADO
### 7.4 Terminación de contrato
Al terminar un contrato:
1. Seleccionar causa de terminación
2. **Automáticamente:** la propiedad pasa a estado VACIO
3. El contrato se marca como RESCINDIDO
## 8. Gestión de Ingresos
### 8.1 Listado de ingresos
Muestra: número de recibo, propiedad, inquilino, importe bruto/neto, fechas de emisión/vencimiento/pago, estado.
### 8.2 Estados de ingreso
| Estado | Descripción |
|--------|-------------|
| PENDIENTE | Emitido pero no cobrado |
| PAGADO | Cobrado |
| VENCIDO | Fecha de vencimiento superada sin cobro |
| PARCIAL | Cobro parcial |
| ANULADO | Anulado |
### 8.3 Registrar pago
1. Localizar el ingreso en el listado
2. Hacer clic en "Registrar pago"
3. El sistema actualiza automáticamente:
- Estado a PAGADO
- Fecha de pago
- Importe neto (después de retención IRPF)
### 8.4 Categorías de ingreso
| Categoría | Descripción |
|-----------|-------------|
| ALQUILER | Pago de renta mensual o periódica |
| FIANZA | Depósito de garantía |
| GASTOS_COMUNIDAD | Repercusión de gastos de comunidad |
| INTERESES_DEMORA | Intereses por pago fuera de plazo |
| INDEMNIZACION | Indemnización por daños o rescisión |
| OTROS_INGRESOS | Otros ingresos no clasificados |
## 9. Gestión de Gastos
### 9.1 Listado de gastos
Muestra: proveedor, factura, concepto, importe, fechas de emisión/pago y estado.
### 9.2 Categorías de gasto
| Categoría | Descripción |
|-----------|-------------|
| REPARACION | Reparaciones y arreglos |
| MANTENIMIENTO | Mantenimiento preventivo |
| COMUNIDAD | Gastos de comunidad de propietarios |
| IBI | Impuesto de Bienes Inmuebles |
| BASURA | Tasa de basura |
| SUMINISTROS_LUZ | Electricidad y suministro eléctrico |
| SUMINISTROS_GAS | Gas natural, butano, propano |
| SUMINISTROS_OTROS | Agua, internet, telefonía y otros suministros |
| SEGURO | Seguro del inmueble |
| REFORMA | Obras de reforma o mejora |
| GESTION | Gastos de gestión inmobiliaria |
| NOTARIA_REGISTRO | Gastos notariales y de registro |
| PUBLICIDAD | Anuncios y marketing |
| OTROS_GASTOS | Otros gastos no clasificados |
### 9.3 Gastos planificados
Se pueden crear gastos con fecha planificada futura para prever pagos periódicos (ej: IBI anual, seguro).
## 10. Gestión de Incidencias
### 10.1 Listado de incidencias
Muestra: título, propiedad, prioridad, técnico asignado, fecha y estado.
### 10.2 Estados de incidencia
```
SIN_REVISAR → TECNICO_AVISADO → REPARACION_PREVISTA → REPARADO
→ IGNORADO
→ ANULADO
```
### 10.3 Prioridades
| Prioridad | Descripción |
|-----------|-------------|
| BAJA | Puede esperar |
| MEDIA | Atención normal |
| ALTA | Urgencia relativa |
| URGENTE | Atención inmediata |
### 10.4 Flujo de trabajo
1. **Crear incidencia:** Seleccionar propiedad, título, descripción y prioridad
2. **Asignar técnico:** El gestor asigna un técnico responsable
3. **Programar reparación:** Establecer fecha prevista de intervención
4. **Resolver:** Marcar como reparado, ignorado o anulado, con notas de resolución
## 11. Recibos Automáticos
### 11.1 Generación de recibos
El sistema puede generar recibos de dos formas:
- **Individual:** Generar un recibo para un contrato específico indicando fecha de emisión y vencimiento.
- **Mensual (automático):** El día 1 de cada mes a las 06:00, el sistema genera recibos para todos los contratos activos.
### 11.2 Numeración
Cada recibo recibe un número único secuencial por año fiscal con formato: `R-2026-00001`, `R-2026-00002`, etc.
### 11.3 PDF
Cada recibo puede descargarse en formato PDF con:
- Datos del arrendador
- Datos del inquilino
- Datos de la propiedad
- Importe, retención IRPF e importe neto
- Número de recibo y fechas
### 11.4 Envío por email
Los recibos pueden enviarse por email al inquilino con el PDF adjunto. El sistema registra un log de cada envío (destinatario, fecha, éxito/error).
### 11.5 Vencimiento automático
El sistema revisa diariamente los ingresos pendientes cuya fecha de vencimiento ha pasado y los marca como VENCIDOS automáticamente.
## 12. Reportes
### 12.1 Informe mensual Excel
Se puede descargar un informe mensual en formato Excel (.xlsx) que incluye:
- **Ingresos del mes:** listado de cobros
- **Gastos del mes:** listado de pagos
- **Balance:** ingresos - gastos = resultado del mes
### 12.2 Dashboard
El dashboard muestra resúmenes visuales con datos agregados del año en curso.
## 13. Gestión de Documentos
El sistema permite adjuntar documentos a cualquier entidad:
- **Propiedades:** fotos, planos, certificados
- **Contratos:** contratos firmados, anexos
- **Inquilinos:** DNI, CIF
- **Incidencias:** fotos de la avería
- **Ingresos:** justificantes de pago
- **Gastos:** facturas escaneadas
**Formatos soportados:** PDF, imágenes (JPG, PNG), documentos (DOC, DOCX, XLS, XLSX)
## 14. Notificaciones
El sistema genera notificaciones automáticas para:
- Incidencias abiertas y asignadas
- Mantenimiento programado próximo a vencer
- Recibos vencidos
- Contratos próximos a vencer
- Contratos vencidos
Las notificaciones pueden marcarse como leídas individualmente o todas a la vez.
## 15. Consejos y Buenas Prácticas
### 15.1 Organización de propiedades
- Usar la jerarquía para agrupar: Edificio (padre) → Pisos (hijos)
- Asignar referencias únicas y descriptivas
- Mantener actualizado el estado de cada propiedad
### 15.2 Gestión de contratos
- Registrar siempre la fecha de fin, aunque sea estimada
- Usar el IBAN de domiciliación para facilitar cobros recurrentes
- Revisar contratos próximos a vencer con antelación
### 15.3 Control financiero
- Registrar los gastos a medida que se generan, no solo cuando se pagan
- Usar la funcionalidad de gastos planificados para prever pagos periódicos
- Generar recibos mensualmente para mantener la tesorería controlada
- Descargar informes mensuales para llevar un control contable externo
### 15.4 Incidencias
- Incluir fotos y descripciones detalladas al crear incidencias
- Establecer prioridades realistas
- Registrar el coste final de cada reparación para control presupuestario
## 16. Preguntas Frecuentes
**¿Puedo recuperar una propiedad eliminada?**
No, la eliminación es lógica (soft-delete). Un administrador puede reactivarla desde la base de datos.
**¿Cómo se calcula el importe neto de un ingreso?**
`net_amount = amount - tax_withheld`. La retención de IRPF se aplica sobre el importe bruto.
**¿Puedo modificar un recibo ya generado?**
Sí, modificando el ingreso correspondiente desde la sección Ingresos.
**¿Qué ocurre si falla el envío de un email?**
El sistema registra el error en el log de emails. Puede reintentarse manualmente.
**¿Los recibos se generan automáticamente todos los meses?**
Sí, el día 1 de cada mes a las 06:00. Un administrador también puede generar todos los recibos manualmente desde la API.
**¿Puedo tener varias series de numeración de recibos?**
Sí, la tabla `receipt_series` permite múltiples series. Por defecto se crea una serie "RECIBOS" para el año actual.
## 17. Soporte
Para incidencias técnicas o consultas, contactar con el administrador del sistema.