diff --git a/backend/api/v1/modules/core/permissions/seed_v2.py b/backend/api/v1/modules/core/permissions/seed_v2.py new file mode 100644 index 0000000..58cda7d --- /dev/null +++ b/backend/api/v1/modules/core/permissions/seed_v2.py @@ -0,0 +1,27 @@ +"""Registro de permisos base del núcleo (core). + +``PermissionService.sync_permissions`` y ``bootstrap_super_admin`` importan este +módulo por su efecto secundario: dar de alta en el ``PermissionRegistry`` los +permisos transversales del sistema antes de sincronizarlos a la base de datos. +Los permisos de cada dominio (crm, ops, fin) se registran en el +``register_permissions()`` de su propio módulo al importar sus routers. +""" + +from .registry import registry + +MODULE = "core" + + +def register_core_permissions() -> None: + """Da de alta los permisos base del sistema (idempotente).""" + registry.register(code="core.access", description="Acceso al sistema", module=MODULE, action="access") + registry.register(code="core.admin", description="Administración del sistema", module=MODULE, action="admin") + registry.register( + code="core.permissions.manage", + description="Gestionar roles y permisos", + module=MODULE, + action="manage", + ) + + +register_core_permissions() diff --git a/backend/seed_crm.py b/backend/seed_crm.py index 538c582..b955783 100644 --- a/backend/seed_crm.py +++ b/backend/seed_crm.py @@ -33,6 +33,10 @@ from api.v1.modules.fin.invoices import service as invoices_service from api.v1.modules.fin.invoices.dto import PaymentCreate from api.v1.modules.core.permissions.models import CompanyRole, Permission, RolePermission from api.v1.modules.core.permissions.service import PermissionService +# Efecto secundario: poblar el PermissionRegistry con los permisos de cada dominio +import api.v1.modules.crm.permissions # noqa: F401 +import api.v1.modules.ops.permissions # noqa: F401 +import api.v1.modules.fin.permissions # noqa: F401 from core.database import CoreSessionLocal # Deben coincidir con DEV_LOCAL_AUTH_TENANT_ID / DEV_LOCAL_AUTH_COMPANY_ID @@ -383,16 +387,35 @@ def seed_invoicing_and_events(db) -> None: # Carriles del proceso (R-T-07): a qué módulos/acciones puede acceder cada rol. # clave = (code, nombre); valor = función que decide si un permiso pertenece al rol. def _carril_roles() -> dict: + # cada función recibe el CÓDIGO del permiso (str) y decide si pertenece al carril return { - ("ventas", "Ventas"): lambda p: p.module == "crm" or p in {"ops.access", "ops.shipment.view"}, - ("operaciones", "Operaciones"): lambda p: p.module == "ops" - or p in {"crm.access", "crm.account.view", "crm.quote.view", "crm.service_request.view", "fin.access", "fin.invoice.view"}, - ("facturacion", "Facturación"): lambda p: p.module == "fin" - or p in {"ops.access", "ops.shipment.view", "crm.access", "crm.account.view"}, - ("consulta", "Consulta"): lambda p: p.endswith(".access") or p.endswith(".view"), + ("ventas", "Ventas"): lambda c: c.startswith("crm.") or c in {"ops.access", "ops.shipment.view"}, + ("operaciones", "Operaciones"): lambda c: c.startswith("ops.") + or c in {"crm.access", "crm.account.view", "crm.quote.view", "crm.service_request.view", "fin.access", "fin.invoice.view"}, + ("facturacion", "Facturación"): lambda c: c.startswith("fin.") + or c in {"ops.access", "ops.shipment.view", "crm.access", "crm.account.view"}, + ("consulta", "Consulta"): lambda c: c.endswith(".access") or c.endswith(".view"), } +def ensure_company(db) -> None: + """Garantiza la empresa dev (a76.company id=1), requerida por la FK company_id de + los roles/permisos (core.company_roles → a76.company). La plantilla no la crea.""" + from sqlalchemy import text + + exists = db.execute(text("SELECT 1 FROM a76.company WHERE id = :id"), {"id": COMPANY_ID}).first() + if exists: + print(f"• Empresa id={COMPANY_ID} ya existe (a76.company)") + return + db.execute( + text("INSERT INTO a76.company (id, tenant_id) VALUES (:id, :tid)"), + {"id": COMPANY_ID, "tid": TENANT_ID}, + ) + db.execute(text("SELECT setval('a76.company_id_seq', (SELECT MAX(id) FROM a76.company))")) + db.commit() + print(f"✓ Empresa demo id={COMPANY_ID} creada (a76.company)") + + def seed_carril_roles(db) -> None: """Crea los roles por carril del proceso con sus permisos (R-T-07). Idempotente.""" # Poblar el catálogo de permisos desde el registry de código @@ -436,6 +459,7 @@ def main() -> None: seed_suppliers_and_related(db) seed_commercial_and_ops(db) seed_invoicing_and_events(db) + ensure_company(db) seed_carril_roles(db) print("\nSeed CRM completado.") finally: