pasosdeJesus/msip

View on GitHub
doc/iniciar-si-usando-msip.md

Summary

Maintainability
Test Coverage
# Iniciar un sistema de información usando Msip

Para iniciar una aplicación de nombre `minsip`que use **msip** en adJ sugerimos:

- Disponer una plataforma que cumpla
  [los requisitos de instalación](https://gitlab.com/pasosdeJesus/msip/blob/main/doc/requisitos.md)
- Crea una aplicación rails que use la base de datos PostgreSQL. Además
  crea y configura la carpeta  `.bundle` así:
  ```sh
  $ mkdir -p minmsip/.bundle
  $ cat > minmsip/.bundle/config <<EOF
  ---
  BUNDLE_PATH: "/var/www/bundler-miusuario"
  BUNDLE_DISABLE_SHARED_GEMS: "true"
  EOF
  $ rails new minmsip --database=postgresql --javascript=esbuild \
    --skip-active-storage --skip-action-mailbox --skip-action-text \
    --skip-action-cable --skip-docker --skip-keeps --skip-system-test
  ```
  Es posible que el último paso genere algunos mensajes de error por gemas que
  no logra instalar porque requieren permisos de superusuario --entre otras
  esto ocurre con `nokogiri`-- en tal caso anota la versión por
  instalar --ejemplo 1.13.3-- y ejecuta algo como:
  ```
  doas gem install --install-dir /var/www/bundler-miusuario/ruby/3.2 nokogiri -v 1.13.3
  ```
  (lo anterior se simplifica a `gemil nokogiri@1.13.3` si usas [los archivos de configuración para adJ](https://gitlab.com/pasosdeJesus/adJ/-/tree/main/arboldd/usr/local/share/adJ/archconf?ref_type=heads) que recomendamos).

  Después desde el directorio `minmsip` vuelve a ejecutar
  ```sh
  $ bundle install
  ```
- Instala `yarn`
  ```sh
  doas npm install --global yarn
  ```
- Con esto ya deberías poder lanzar la aplicación en modo desarrollo (aunque
  no correrá mucho sin base de datos, así que detenla con Control-C después de
  lanzarla):
  ```sh
  $ bin/rails s
  => Booting Puma
  => Rails 7.1.2 application starting in development
  => Run `rails server --help` for more startup options
  Puma starting in single mode...
  * Version 5.6.5 (ruby 3.2.0-p0) ("Birdie's Version")
  * Min threads: 5
  * Max threads: 5
  * Environment: development
  *         PID: 66138
  * Listening on tcp://[::1]:3000
  * Listening on tcp://127.0.0.1:3000
  Use Ctrl-C to stop
  ```
- Crea el usuario de PostgreSQL y la base de datos de desarrollo que emplearás.
  Por ejemplo en adJ para crear el usuario 'isa5417' con clave 'aquilaclave'
  y la base de datos 'minmsip_des' sería:
  ```sh
  $ doas su - _postgresql
  $ createuser -h /var/www/var/run/postgresql/ -s -Upostgres isa5417
  $ psql -h /var/www/var/run/postgresql/ -Upostgres
  postgres=# ALTER USER isa5417 WITH PASSWORD 'aquilaclave';
  postgres=# \q
  $ exit
  $ createdb -h/var/www/var/run/postgresql/ -Uisa5417 minmsip_des
  ```
  Puedes probar el ingreso con la interfaz de línea de ordenes psql con:
  ```
  $ psql -h/var/www/var/run/postgresql/ -Uisa5417 minmsip_des
  Password for user isa5417:
  psql (15.4)
  Type "help" for help.
  [local:/var/www/var/run/postgresql/] msipdes@minmsip_des=#
  ```
  Para evitar que te solicite clave del usuario PostgreSQL en cada ingreso a
  `psql` puedes crear o agregar a tu archivo `~/.pgpass` la línea:
  ```
  *:*:*:isa5417:aquilaclave
  ```

- Si hace falta instala globalmente las gemas `dotenv` y `foreman` con
  `doas gem install dotenv foreman` e incluye otras gemas necesarias, así como
  `msip` en el archivo `Gemfile` que debe quedar al menos con:
  ```
  source "https://rubygems.org"

  git_source(:gitlab) { |repo| "https://gitlab.com/#{repo}.git" }

  ruby ">=3.2.0"

  gem "babel-transpiler"           # Permite tener módulos ES6

  gem "bcrypt"                     # Condensando de claves con bcrypt

  gem "bootsnap", require: false   # Arranque rápido

  gem "cancancan"                  # Control de acceso

  gem "cocoon", git: "https://github.com/vtamara/cocoon.git",
  branch: "new_id_with_ajax"       # Formularios anidados por reemplazar con turbo

  gem "coffee-rails"               # Parte de msip aún usa Coffescript

  gem "devise"                     # Autenticación

  gem "devise-i18n"                # Localización e Internacionalización

  gem "dotenv-rails"               # Configuración de servidor en .env

  gem "jbuilder"                   # Json

  gem "jsbundling-rails"

  gem "kt-paperclip"               # Anexos

  gem "nokogiri"                   # Procesamiento XML

  gem "pg"                         # PostgreSQL

  gem "puma"                       # Lanza en modo desarrollo

  gem "rails"

  gem "rails-i18n"                 # Localización e Internacionalización

  gem "sassc-rails"                # Conversión a CSS

  gem "simple_form"                # Formularios

  gem "sprockets-rails"            # Tuberia de recursos

  gem "stimulus-rails"             # Libreria javascript

  gem "turbo-rails"                # Acelera HTML

  gem "twitter_cldr"               # Localización e internacionalización

  gem "will_paginate"              # Pagina listados


  #### Motores que sobrecargan vistas o basados en MSIP en orden de apilamento
  gem "msip",                       # SI estilo Pasos de Jesús
    git: "https://gitlab.com/pasosdeJesus/msip.git"


  group :development, :test do
    gem "code-scanning-rubocop"   # Busca fallas de seguridad

    gem "colorize"

    gem "debug"                   # Depura

    gem "rails-erd"               # Genera diagrama entidad asociación
  end

  group :development do
    gem "web-console"             # Depura en navegador
  end

  group :test do
    gem "minitest"                # Pruebas automáticas de regresión con minitest

    gem "rails-controller-testing"

    gem "simplecov"               # Mide cobertura de pruebas de regresión
  end
  ```
  Y después instala las nuevas gemas con:
  ```
  $ bundle install
  ```

- Como msip emplea `dotenv` y `dotenv-rails` crea el archivo `.env` con
  algunas configuraciones a nivel de servidor como el usuario para
  PostgreSQL, su clave, así como los nombres que usarás para las bases
  de datos de pruebas, desarrollo y producción (la de desarrollo debe
  coincidir con la creada anteriormente):
  ```
  #!/bin/sh

  export BD_SERVIDOR=/var/www/var/run/postgresql    # Ruta al socket de PostgreSQL
  export BD_USUARIO=isa5417                         # Usuario PostgreSQL
  export BD_CLAVE=aquilaclave                       # Clave PostgreSQL
  export BD_DES=minmsip_des                          # Base de datos de desarrollo
  export BD_PRUEBA=minmsip_pru                       # Base de datos de pruebas
  export BD_PRO=minmsip_pro                          # Base de datos de producción (no requerida en desarrollo)

  export CONFIG_HOSTS=127.0.0.1          # Nombre del servidor donde se correrá
  export RUTA_RELATIVA=/minmsip/                     # Ruta en la que correra, puede ser /
  export DIRAP=${HOME}/comp/rails/minmsip            # Directorio donde están las fuentes de la aplicación
  export RAILS_ENV=development                      # Modo en el que correrá (podría ser también production)

  export IPDES=127.0.0.1                            # En modo desarrollo IP en la que escuchará conexiones
  export PUERTODES=3300                             # En modo desarrollo puerto en el que escuchará conexiones
  export MAQRECVIVA=$CONFIG_HOSTS                   # Servidor para recarga viva
  export PUERTORECVIVA=4600                         # Puerto para recarga viva


  export MSIP_FORMATO_FECHA="dd/M/yyyy"              # Formato para presentar y recibir fechas, también podría ser yyyy-mm-dd
  export MSIP_RUTA_ANEXOS=${DIRAP}/archivos/anexos
  export MSIP_RUTA_VOLCADOS=${DIRAP}/archivos/bd
  ```
  Puedes verificar la sintaxis cargando ese archivo desde la línea de ordenes
  y revisando alguna variable con:
  ```
  $ (. .env; echo $BD_USUARIO)
  isa5417
  ```
  Para dar posibilidad de sobrecargar esas variables de configuración del
  servidor desde la línea de órdenes, cada una debe ponerse dentro de un
  `if` como en el ejemplo siguiente con la primera:
  ```
  if (test "$BD_SERVIDOR" = "") then {
    export BD_SERVIDOR=/var/www/var/run/postgresql # Ruta socket de PostgreSQL
  }
  ```
  No agregues este archivo al repositorio git (si emplearás uno) porque tiene
  claves y datos sensibles que podrían usarse para atacar tu servidor.
  Puedes ayudarte a evitarlo agreando `.env` a tu archivo `.gitignore`
  ```
  echo .env >> .gitignore
  ```
  Pero si quieres distribuir en tu repositorio una plantilla del archivo .env
  puedes copiarlo en `.env.plantilla` y agregar `.env.plantilla` a
  tu repositorio:
  ```
  cp .env .env.plantilla
  vi .env.plantilla  # Edita para quitar informacion sensible y poner ejemplos
  git add .env.plantilla
  ```

- Como incluiste la gema `msip` en tu `Gemfile`, debes crear el control de
  acceso en el archivo ```app/models/ability.rb``` inicialmente con:
  ```rb
  class Ability  < Msip::Ability


    # Se definen habilidades con cancancan
    # @usuario Usuario que hace petición
    def initialize(usuario = nil)
      # El primer argumento para can es la acción a la que se da permiso,
      # el segundo es el recurso sobre el que puede realizar la acción,
      # el tercero opcional es un diccionario de condiciones para filtrar
      # más (e.g :publicado => true).
      #
      # El primer argumento puede ser :manage para indicar toda acción,
      # o grupos de acciones como :read (incluye :show e :index),
      # :create, :update y :destroy.
      #
      # Si como segundo argumento usa :all se aplica a todo recurso,
      # o puede ser una clase.
      #
      # Detalles en el wiki de cancan:
      #   https://github.com/ryanb/cancan/wiki/Defining-Abilities

      # Sin autenticación puede consultarse información geográfica
      can :read, [Msip::Pais, Msip::Departamento, Msip::Municipio, Msip::Centropoblado]
      # No se autorizan usuarios con fecha de deshabilitación
      if !usuario || usuario.fechadeshabilitacion
        return
      end
      can :contar, Msip::Ubicacion
      can :buscar, Msip::Ubicacion
      can :lista, Msip::Ubicacion
      can :descarga_anexo, Msip::Anexo
      can :nuevo, Msip::Ubicacion
      if usuario && usuario.rol then
        case usuario.rol
        when Ability::ROLANALI
          can :read, Msip::Orgsocial
          can :read, Msip::Persona
          can :read, Msip::Ubicacion
          can :new, Msip::Ubicacion
          can [:update, :create, :destroy], Msip::Ubicacion
        when Ability::ROLADMIN
          can :manage, Msip::Orgsocial
          can :manage, Msip::Persona
          can :manage, Msip::Respaldo7z
          can :manage, Msip::Ubicacion
          can :manage, ::Usuario
          can :manage, :tablasbasicas
          self.tablasbasicas.each do |t|
            c = Ability.tb_clase(t)
            can :manage, c
          end
        end
      end
    end # def initialize

  end
  ```
  Verifica la sintaxis con:
  ```
  ruby -c app/models/ability.rb
  ```


- Modifica el archivo `config/database.yml` empleando las variables de
  configuración que usaste en `.env`:
```yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("BD_USUARIO") %>
  password: <%= ENV.fetch("BD_CLAVE") %>
  host: <%= ENV.fetch("BD_SERVIDOR") %>

development:
  <<: *default
  database: <%= ENV.fetch("BD_DES") %>

test:
  <<: *default
  database: <%= ENV.fetch("BD_PRUEBA") %>

production:
  <<: *default
  database: <%= ENV.fetch("BD_PRO") %>
```
A continuación prueba que puedes ingresar a la interfaz `psql` de la base de
desarrollo pero mediante rails:
```sh
$ bin/rails dbconsole
psql (15.4)
Type "help" for help.

minmsip_des=# \q
```

- Modifica la configuración de `config/application.rb` asegurando
  emplear volcados SQL, estableciendo zona horaria, localización,
  formato de la fecha, etc. por ejemplo:
  ```rb
  require_relative "boot"
  require "rails"
  # Elige los marcos de trabajo que necesitas:
  require "active_model/railtie"
  require "active_job/railtie"
  require "active_record/railtie"
  # require "active_storage/engine"
  require "action_controller/railtie"
  require "action_mailer/railtie"
  # require "action_mailbox/engine"
  # require "action_text/engine"
  require "action_view/railtie"
  require "action_cable/engine"
  require "rails/test_unit/railtie"

  Bundler.require(*Rails.groups)

  module Minmsip
    class Application < Rails::Application
      config.load_defaults 7.1

      config.active_record.schema_format = :sql
      config.railties_order = [:main_app, Msip::Engine, :all]

      config.time_zone = 'America/Bogota'
      config.i18n.default_locale = :es

      config.relative_url_root = ENV.fetch('RUTA_RELATIVA', '/minmsip/')

      config.x.formato_fecha = ENV.fetch('MSIP_FORMATO_FECHA', 'dd/M/yyyy')
      config.hosts.concat(
        ENV.fetch('CONFIG_HOSTS', '127.0.0.1').downcase.split(',')
      )
    end
  end
  ```
  Verifica la sintaxis tras cada modificación con:
  ```
  % ruby -c config/application.rb
  ```
  usa la convención de msip de dejar nombres de tablas en singular agregando
  a `config/environment.rb`:
  ```
  ActiveRecord::Base.pluralize_table_names=false
  ```
  y verifica que carga correctamente con:
  ```
  % bin/rails console
  Loading development environment (Rails 7.1.2)
  irb(main):001:0> Rails.configuration.x.formato_fecha
  => "dd/M/yyyy"
  ```

- Copia la estructura de la base de datos mínima
  ```sh
  $ ftp -o db/structure.sql https://gitlab.com/pasosdeJesus/msip/-/raw/main/test/dummy/db/structure.sql
  ```
- Prepara como semillas para la base de datos las semillas incluidas en
  `msip` y un primer usuario `msip` con clave `msip`,
  modificando `db/seeds.rb` para que sea:
  ```rb
  conexion = ActiveRecord::Base.connection();

  Msip::carga_semillas_sql(conexion, "msip", :datos)

  conexion.execute("INSERT INTO public.usuario
    (id, nusuario, email, encrypted_password, password,
      fechacreacion, created_at, updated_at, rol)
    VALUES (1, 'msip', 'msip@localhost',
      '$2a$10$WphwnqY9mO/vGcIEUuWVquetUqBd9kqcbnUFmxpbPuYRUs8FwdArW',
      '', '2022-09-30', '2022-09-30', '2022-09-30', 1);")
  ```
- Ahora borra base, inicializala, carga semillas y prepara índices con:
  ```sh
  $ bin/rails db:drop db:setup db:seed msip:indices
  ```
  Prueba lo que llevas en la base de datos iniciando consola interactiva de
  PostgreSQL y realizando una consulta:
  ```$
  $ bin/rails dbconsole
  psql (15.4)
  Type "help" for help.

  minmsip_des=# select count(*) from msip_centropoblado;
   count
  -------
   44501
  (1 row)

  minmsip_des=# \q
  ```
- Crea el modelo `usuario` en `app/models/usuario.rb` inicialmente con:
  ```rb

  require "msip/concerns/models/usuario"
  class Usuario < ActiveRecord::Base
    include Msip::Concerns::Models::Usuario
  end
  ```
  Puedes probar que el modelo y revisar que se haya creado el usuario
  especificado en `db/seed.rb` en una consola irb:
  ```sh
  $ bin/rails console
  irb(main):001:0> Usuario.all.count
     (0.7ms)  SELECT COUNT(*) FROM "usuario"
  => 1
  irb(main):002:0> Usuario.all[0]
    Usuario Load (0.3ms)  SELECT "usuario".* FROM "usuario"
  => #<Usuario nusuario: "msip", password: [FILTERED], descripcion: nil, rol: 1, idioma: "es_CO", id: 1, fechacreacion: "2014-08-14", fechadeshabilitacion: nil, email: "msip@localhost", created_at: "2014-08-13 19:00:00.000000000 -0500", updated_at: "2014-08-13 19:00:00.000000000 -0500", nombre: nil, tema_id: nil>
  irb(main):003:0> exit
  ```
- Crea un controlador para usuarios en `app/controllers/usuarios_controller.rb`
  inicialmente con:
  ```rb
  require "msip/concerns/controllers/usuarios_controller"

  class UsuariosController < Msip::ModelosController
    include Msip::Concerns::Controllers::UsuariosController

    def index
      super
    end
  end
  ```
  puedes probar desde `bin/rails console` con:
  ```
  irb(main):001:0> UsuariosController
  => UsuariosController
  ```
  Posteriormente puedes ver como personalizar el modelo y el controlador
  del usuario en
  <https://gitlab.com/pasosdeJesus/msip/blob/main/doc/modelo-usuario.md>.

- Para establecer rutas de anexos y de volcados crea dos directorios según
  hayas configurado en `.env` (ej. `mkdir -p archivos/{anexos,bd}`)
  y crea el archivo `config/initializers/msip.rb` con algo como:
  ```rb
  Msip.setup do |config|
    config.ruta_anexos = ENV.fetch(
      "MSIP_RUTA_ANEXOS", "#{Rails.root}/archivos/anexos/")
    config.ruta_volcados = ENV.fetch(
      "MSIP_RUTA_VOLCADOS", "#{Rails.root}/archivos/bd/")
    config.titulo = "Aplicación mínima que usa MSIP"
  end
  ```
- Remplaza `app/controllers/application_controller.rb` por
  ```rb
  class ApplicationController < Msip::ApplicationController
    # Previente ataques CSRF elevando una excepción
    # En el caso de APIs, en cambio puedes querer usar :null_session
    protect_from_forgery with: :exception
  end
  ```
  - En este punto deberías poder arrancar la aplicación en modo desarrollo por
    ejemplo escuchando en el puerto 3000 de 127.0.0.1 con:
  ```sh
  $ bin/rails s -p 3000 -b 127.0.0.1
  ```
  y verla operando en un navegador en la dirección http://localhost:3000
  presentando la página por omisión de rails.
  Detén la aplicación con Control-C para continuar configurando.
- Para ver el pantallazo inicial en en la ruta (o punto de montaje) `/minmsip/`
  (sin menús, ni una maquetación con bootstrap) debes configurar rutas en
  `config/routes.rb`
  ```rb
  Rails.application.routes.draw do
    rutarel = ENV.fetch('RUTA_RELATIVA', 'minmsip/')
    scope rutarel do
      devise_scope :usuario do
        get 'sign_out' => 'devise/sessions#destroy'
        if (Rails.configuration.relative_url_root != '/')
          ruta = File.join(Rails.configuration.relative_url_root,
                           'usuarios/sign_in')
          post ruta, to: 'devise/sessions#create'
          get  ruta, to: 'devise/sessions#new'
        end
      end

      devise_for :usuarios, :skip => [:registrations], module: :devise
      as :usuario do
        get 'usuarios/edit' => 'devise/registrations#edit',
          :as => 'editar_registro_usuario'
        put 'usuarios/:id' => 'devise/registrations#update',
          :as => 'registro_usuario'
      end
      resources :usuarios, path_names: { new: 'nuevo', edit: 'edita' }

      root 'msip/hogar#index'
    end  # scope
    mount Msip::Engine, at: rutarel, as: 'msip'
  end
  ```
  Verifica su sintaxis con `ruby -c config/routes.rb`

  En `config/initializers/punto_montaje.rb`:
  ```
  # relative_url_root configurada en config/application.rb
  Minmsip::Application.config.assets.prefix = ENV.fetch(
    'RUTA_RELATIVA', '/minmsip') == '/' ?
   '/assets' : (ENV.fetch('RUTA_RELATIVA', '/minmsip') + '/assets')
  ```
  y preparar directorio `public/minmsip`:
  ```
  mkdir public/minmsip
  ```
- Para preparar experiencia de usuario con Bootstrap 5, Javascript con
  módulos, Turbo, Stimulus, autocompletación y partes legadas con Jquery y cocoon debes instalar paquetes `npm` mínimos:
  ```sh
  yarn add @hotwired/stimulus @hotwired/turbo-rails @rails/ujs \
    @popperjs/core @fortawesome/fontawesome-free bootstrap \
    https://gitlab.com/pasosdeJesus/autocompleta_ajax.git \
    tom-select esbuild popper.js@2.0.0-next.4 jquery
  yarn add -D  babel-plugin-macros
  yarn add https://gitlab.com/pasosdeJesus/autocompleta_ajax.git
  yarn install
  ```
  En `app/javascript/application.js` debes configurar como cargar los módulos
  javascript, dejando jQuery de manera global (mientras ̣`msip` deja de
  depender de ese paquete), inicialmente puedes copiar el de la aplicación
  de ejemplo de msip:

  ```
  $ (cd app/javascript && ftp https://gitlab.com/pasosdeJesus/msip/-/raw/main/test/dummy/app/javascript/{application.js,jquery.js})
  ```
  Asegura que se podrán usar funciones auxiliares relacionadas con Bootstrap,
  dejando `app/helpers/application_helper.rb` con el siguiente contenido:
  ```rb
  module ApplicationHelper

    include Msip::BootstrapHelper

  end
  ```
  Para empaquetar Javascript con esbuild agrega al comienzo de `package.json`:
  ```
    "scripts": {
      "build": "esbuild app/javascript/*.* --preserve-symlinks --bundle --sourcemap --outdir=app/assets/builds",
      "start": "node esbuild-des.config.js"
    },
  ```

- Emplea los controladores stimulus de msip con:
  ```
    mkdir app/javascript/controllers
    bin/rails msip:stimulus_motores
  ```
  si hace falta crea el archivo `app/javascript/controllers/application.js` con:
  ```

  import { Application } from "@hotwired/stimulus"

  const application = Application.start()

  application.debug = false
  window.Stimulus   = application

  export { application }
  ```

  Y verifica que la configuración de javascript con módulos y `esbuild` es
  correcta empaquetando en `app/assets/builds/application.js` con:
  ```
  touch app/javascript/controllers/index.js
  yarn build
  ```
- La tubería de recursos (i.e sprockets) se encargará de ubicar en un
  directorio `public/minmsip/assets/images` el logo (`logo.jpg`) y los
  favicons que pongas en la ruta `app/assets/images`. Inicialmente puedes
  copiar allí los de la aplicación de prueba de msip con
  ```
  cd app/assets/images
  ftp https://gitlab.com/pasosdeJesus/msip/-/raw/main/test/dummy/app/assets/images/{logo.jpg,browserconfig.xml,favicon.ico}
  ftp https://gitlab.com/pasosdeJesus/msip/-/raw/main/test/dummy/app/assets/images/favicon-{114,120,144,150,152,16,160,180,192,310,32,57,60,64,70,72,76,96}.png
  cd ../../..
  ```
  Sprockets también servirá los javascript que pongas en `app/assets/javascripts`
  y el javascript modular empaquetado por esbuild en `app/assets/build`.

  Puedes usar sprockets para transmitir hojas de estilo preconfiguradas de
  msip y sobrecargables en `app/assets/stylesheets/` dejando en
  `app/assets/stylesheets/application.css`:
  ```css
  /*
   *= require msip/application.css
   *= require_tree .
   *= require_self
   */
  ```
  Para cargar otros javascript de msip que no se manejen con esbuild, asegurate
  de dejar en `app/assets/javascripts/recursos_sprockets.js` el siguiente
  contenido (creando antes el directorio `app/assets/javascripts`):
  ```js
  //= require msip/application
  //= require_tree .
  ```
  En `app/assets/config/manifest.js` indica recursos por incluir.
  Por ejemplo javascript en espacio global manejado por sprockets
  (incluyendo los de motores) y el modular empaquetado con esbuild.
  ```
  //= link_tree ../images
  //= link_directory ../javascripts .js
  //= link_directory ../stylesheets .css
  //= link application.css
  //= link recursos_sprockets.js
  //= link recursos_sprockets.js.map
  //= link_tree ../builds
  ```
  Tras esto deberías poder precompilar recursos con:
  ```
  bin/rails assets:precompile --trace
  ```
  que los debe crear en `public/minmsip/assets`

- Para usar la maquetación predeterminada de msip, y sólo modificar el menú
  deja `app/views/layouts/application.html.erb` como se presenta a continuación
  (nota que usamos funciones auxiliares para generar HTML con clases de
  bootstrap, se pueden emplear también sus formas originales en inglés basadas
  en las de la gema `twitter-bootstrap-rails`):
  ```erb
  <% content_for :titulo do %>
      <%= Msip.titulo %>
  <% end %>

  <% content_for :menu do %>
     <%= grupo_menus do %>
      <% if !current_usuario.nil? %>
          <%= opcion_menu "Organizaciones sociales", msip.orgsociales_path %>
          <%= opcion_menu "Personas", msip.personas_path %>
      <% end %>
    <% end %>
    <%= grupo_menus :empuja => :derecha do %>
      <%= opcion_menu "Documentacion", "https://gitlab.com/pasosdeJesus/msip/tree/main/doc/README.md" %>
      <% if current_usuario %>
        <%= despliega_abajo "Administrar" do %>
          <%= opcion_menu "Clave", main_app.editar_registro_usuario_path, desplegable: true %>
          <%= opcion_menu "Copia de respaldo cifrada", msip.respaldo7z_path, desplegable: true %>
          <%= opcion_menu "Usuarios", main_app.usuarios_path, desplegable: true %>
          <%= opcion_menu "Tablas Básicas", msip.tablasbasicas_path, desplegable: true %>
          <%= opcion_menu "Ayuda CA", msip.ayuda_controldeacceso_path, desplegable: true %>
        <% end %>
        <%= opcion_menu "Salir #{current_usuario.nusuario}", main_app.sign_out_path %>
      <% else %>
        <%= opcion_menu "Acerca de", msip.acercade_path %>
        <%= opcion_menu "Iniciar Sesión", main_app.new_usuario_session_path %>
      <% end %>
    <% end %>
  <% end %>

  <% content_for :piedepagina do %>
    <p><span class='derechos'><a href="https://www.pasosdejesus.org/dominio_publico_colombia.html">Dominio Público de acuerdo a Legislación Colombiana</a><br/>
      Desarrollado por <a href="https://www.pasosdeJesus.org" target="_blank">Pasos de Jesús</a>. 2023.
    </span></p>
  <% end %>

  <%= render template: "layouts/msip/application" %>
  ```

- Si faltaba, lanza la aplicación en modo desarrollo con:
  ```sh
  $ bin/rails s -b 127.0.0.1 -p 3000
  ```
  y examínala en el puerto 3000 con tu navegador <http://localhost:3000/minmsip>,
  recuerda que el usuario inicial es `msip` con clave `msip`.

- Para facilitar arrancar, detener o actualizar sugerimos copiar de las fuentes
  `msip` los guiones `corre`, `detiene` y `migra` del directorio `test/dummy/bin`
  en el directorio `bin` de tu aplicación.
  Así podrás configurar puerto e IP en .env e iniciar con:
  ```
  bin/corre
  ```
  o sin precompilar recursos (rápido) con:
  ```
  R=1 bin/corre
  ```
  Este script `bin/corre` además ejecutará la tarea rake
  `msip:stimulus_motores` que enlaza controladores stimulus de motores
  en subdirectorios de tu directorio `app/javascript/controllers`.

  Por otra parte si creas el archivo `Procfile` con:
  ```
  rails: R=f bin/corre
  js: yarn start --watch
  ```
  y copias el archivo `esbuild-des.config.js` de `msip/test/dummy`,
  al ejecutar `bin/corre` se usará `foreman` para lanzar dos procesos: uno
  para la ejecución de rails y otro para realizar recargas vivas
  de sus modificaciones a `app/javascript` (es decir no tendrá que detener la
  aplicación, reempaquetarla con esbuild y recargarla en el navegador porque
  todo esto se hará automáticamente cada vez que modifique una fuente
  en `app/javascript`).