Ciclo tecnico

Como funciona realmente un modulo dentro del CRM

Esta pagina explica la parte de codigo que faltaba: que archivos tiene un modulo, para que sirve cada uno y en que orden participa cuando el sistema arranca o cuando se despliega una DLL.

Estructura minima de un modulo

Modules/MIMODULO
  Controllers/
  Data/
    Configurations/
    Migrations/
    MIMODULODbContext.cs
  Entities/
  Hooks/
  Seed/
  Services/
  Views/
  ModuleDescriptor.cs
  ModuleInitializer.cs

No todos los modulos necesitan todas las carpetas, pero esa es la estructura recomendada para un modulo funcional completo.

ModuleDescriptor: para que sirve

ModuleDescriptor describe el modulo. Es metadato, no logica de negocio.

  • Identificador del modulo
  • Nombre visible
  • Version
  • Dependencias requeridas
  • DbContextType si el modulo tiene base de datos
  • Si tiene seeds o no
public static class ModuleDescriptor
{
    public static ModuleMetadata Metadata => new ModuleMetadata(
        id: "PROVEEDORES",
        name: "PROVEEDORES",
        version: "1.0.0",
        requiredModules: new[] { "ENTIDADES" },
        optionalModules: Array.Empty<string>(),
        dbContextType: typeof(PROVEEDORESDbContext)
    );
}

ModuleInitializer: para que sirve

ModuleInitializer registra servicios del modulo en el contenedor DI.

  • Servicios del modulo
  • Hooks del modulo
  • Seeds del modulo si hace falta
public static class ModuleInitializer
{
    public static void Register(IServiceCollection services)
    {
        services.AddScoped<IProveedorService, ProveedorService>();
        services.AddScoped<IClienteAccionHook, ProveedorClienteAccionHook>();
        services.AddScoped<IClienteTabHook, ProveedorClienteTabHook>();
    }
}
Regla: ModuleDescriptor describe, ModuleInitializer registra.

Que pasa al arrancar la aplicacion

  1. El cargador descubre modulos.
  2. Lee su ModuleDescriptor.
  3. Registra su DbContext si existe.
  4. Ejecuta ModuleInitializer.Register(...).
  5. El gestor de migraciones intenta aplicar esquema del modulo.
  6. Despues se ejecutan seeds de datos base.

Que pasa al subir una DLL desde ADMINPANEL

  1. La DLL se despliega en ModulosCompilados.
  2. Se valida que tenga ModuleDescriptor o ModuleInitializer.
  3. El modulo queda preparado en runtime.
  4. Para registro completo de servicios, rutas MVC, migraciones y seeds se requiere reiniciar la aplicacion.
La carga en caliente no recompone completamente DI, rutas MVC y el pipeline ya construido. La activacion completa sigue ocurriendo al reiniciar.

Ver pagina especifica del sistema ModulosCompilados

DbContext del modulo

El DbContext del modulo es dueno de sus tablas.

public class MIMODULODbContext : DbContext
{
    public DbSet<MiEntidad> MiEntidades => Set<MiEntidad>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(MIMODULODbContext).Assembly);
    }
}

Services del modulo

La logica de negocio no debe estar metida en controller ni en hook. Debe vivir en servicios del modulo.

  • CRUD principal
  • Validaciones del modulo
  • Casos de uso
  • Reglas de negocio

Controllers del modulo

Los controllers exponen la UI o la API del modulo.

  • Paginas MVC del modulo
  • Endpoints AJAX
  • Acciones de detalle, index, crear, editar

No deben ser el sitio principal de la logica compleja.

Seeds del modulo

El seed debe usarse para bootstrap de datos, no como sustituto permanente de migraciones.

  • Registrar modulo
  • Crear permisos
  • Sembrar menu
  • Sembrar catalogos base

Permisos del modulo

Un modulo debe declarar permisos coherentes y usarlos de forma consistente.

new { Key = "PROVEEDORES.Ver", Nombre = "Ver proveedores" }
new { Key = "PROVEEDORES.Editar", Nombre = "Editar proveedores" }

La misma clave debe coincidir en seed, atributo de controller y servicios de autorizacion.

Menu del modulo

El modulo puede exponer menu de dos formas:

  • definicion sincronizada en BD
  • atributos tipo MenuEntry como fuente tecnica

La idea buena es: codigo como definicion base, BD como personalizacion.

Limites de modulo

Un modulo puede necesitar limites funcionales por plan o por empresa. Ejemplos:

  • maximo de almacenes
  • maximo de usuarios del modulo
  • maximo de documentos al mes
  • si una opcion premium esta activa o no

Como debe pensarse

  • El modulo define que limites existen.
  • El sistema core o admin guarda esos limites por plan o empresa.
  • El servicio del modulo consulta el limite antes de ejecutar la accion.
El limite no debe estar hardcodeado en la vista. Debe validarse en backend.

Configuracion de modulo

Algunos modulos necesitan opciones configurables. Ejemplos:

  • serie por defecto
  • almacen por defecto
  • activar o no una funcionalidad
  • modo de trabajo del modulo

Regla recomendada

  • la configuracion global del sistema vive en tablas core
  • la configuracion funcional del modulo debe modelarse como opcion o tabla del modulo
  • el modulo debe exponer su servicio de configuracion
public interface IModuloConfiguracionService
{
    Task<string?> GetValorAsync(string moduloKey, int empresaId, string clave);
    Task SetValorAsync(string moduloKey, int empresaId, string clave, string? valor);
}

Opciones del modulo

Una opcion es una capacidad concreta activable o parametrizable dentro del modulo. Ejemplos:

  • usar lotes
  • usar tallas y colores
  • usar doble aprobacion
  • activar documentos avanzados

Las opciones suelen combinarse con:

  • permisos
  • limites
  • configuracion por empresa o plan

Hooks del modulo

Si un modulo necesita integrarse en otro host, crea hooks.

  • Tabs en fichas
  • Acciones extra
  • Inicializacion por evento, por ejemplo alta de empresa
services.AddScoped<IClienteTabHook, ProveedorClienteTabHook>();
services.AddScoped<IClienteAccionHook, ProveedorClienteAccionHook>();

Cuando un modulo depende de otro

Si un modulo necesita otro maestro, debe declararlo como dependencia.

  • PROVEEDORES depende de ENTIDADES
  • COMPRAS dependera de PROVEEDORES y PRODUCTOS
No mezcles dependencias ocultas. Si un modulo necesita otro para funcionar, debe declararse y modelarse claramente.

Resumen del ciclo de vida

1. Crear entidades
2. Crear configuraciones EF
3. Crear DbContext
4. Declarar ModuleDescriptor
5. Registrar servicios en ModuleInitializer
6. Crear migracion
7. Crear seed de datos base
8. Anadir controllers y vistas
9. Anadir hooks si el modulo extiende otra pantalla
10. Verificar permisos, menu y esquema