# 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.