// {} Librerías e importaciones

// ! Utilidades
import {
  pluralizarFrase,
  mascara,
  parametros,
  capitalizarLetra,
  formatoNumerico,
  sumar,
  peticion
} from '../utilidades.js';

// ! Selector
import Selector from '../aplicacion/elementos/selector.js';

// ! Tabla
import Tabla from '../aplicacion/elementos/tabla.js';

// {}
(async () => {

  // ! Declaración de variables
  let
    campos = {
      limpiar: {
        funcion: (campos) => campos && campos.forEach(campo => {

          if (campo.type === 'hidden')
            return
          ;

          campo = {
            elemento: campo,
            valor: campo.dataset.campoValorPredeterminado,
            componente: campo.closest('.input-group')?.querySelector('.input-group-text'),
            mascara: window.aplicacion.campo.mascara[campo.id]
          }

          if (campo.elemento.classList.contains('is-invalid'))
            campo.elemento.classList.remove('is-invalid')
          ;

          if (formulario.campos.prevenir.includes(campo.elemento))
            return
          ;

          if (campo.valor)
            (campo.mascara ?? campo.elemento).value = campo.valor
          else if (campo.mascara)
            campo.mascara.value = campo.elemento.dataset.mascara === 'numerico' ? '0' : ''
          else
            campo.elemento.options
              ? campo.elemento.selectedIndex = 0
              : campo.elemento.value = null
          ;

          if (campo.elemento.type === 'textarea')
            campo.elemento.dispatchEvent(new Event('input'))
          else if (campo.elemento.type === 'select-one') {

            campo.elemento.disabled =
              campo.elemento.hasAttribute('disabled') ||
              campo.elemento.options?.length <= 1
            ;

            $(campo.elemento).trigger('change')

          }

          if (campo.componente)
            campo.componente.value = null

        })
      },
      mutar: {
        tipos: ['input', 'select', 'textarea'],
        funcion: async (elemento) => {

          if (!elemento.id) {

            elemento.id = crypto.randomUUID();

            elemento.previousElementSibling?.setAttribute('for', elemento.id)

          }

          switch (elemento.type) {

            case 'checkbox':

              elemento.value = Number(elemento.checked);

              elemento.addEventListener('change', function(evento) { elemento.value = Number(elemento.checked) })

            break;

            case 'date':

              if (window.aplicacion.programa.generico)
                return
              ;

              elemento.addEventListener('focusout', function(evento) {

                let fecha = {
                  minima: Date.parse(elemento.min),
                  valor: Date.parse(elemento.value),
                  maxima: Date.parse(elemento.max)
                }

                if (fecha.valor < fecha.minima)
                  elemento.value = elemento.min
                else if (fecha.valor > fecha.maxima)
                  elemento.value = elemento.max
                else if (!window.aplicacion.ruta.includes('reportes') && isNaN(fecha.valor))
                  elemento.value =
                    elemento.dataset.campoValorPredeterminado ??
                    new Date().toLocaleDateString('en-CA')

              })

            break;

            case 'select-one':

              if (!window.aplicacion.campo.selector[elemento.id]) {

                window.aplicacion.campo.selector[elemento.id] = new Selector({ selector: elemento });

                await window.aplicacion.campo.selector[elemento.id].inicializar()

              }

              if (elemento.dataset.peticionTabla || elemento.dataset.peticionProcedimiento)
                window.aplicacion.campo.selector[elemento.id].peticion('opciones')
              else
                elemento.disabled = elemento.disabled || elemento.options?.length <= 1

            break;

            case 'textarea':

              elemento.addEventListener('input', function(evento) {

                elemento.style.height = 'auto';
                elemento.style.height = elemento.scrollHeight + 'px'

              })

            break

            default:

              if (elemento.dataset.mascara && !window.aplicacion.campo.mascara[elemento.id])
                window.aplicacion.campo.mascara[elemento.id] = mascara(elemento)

            break;

          }

          if (elemento.value && !elemento.dataset.campoValorPredeterminado)
            elemento.dataset.campoValorPredeterminado = elemento.value

        }
      }
    },
    tabla = {
      codificacion: {
        elemento: $('#tablaRegistrosCodificacion'),
        fila: {
          datos: {}
        }
      }
    },
    formulario = {},
    ventana = {
      emergente: { inicializador: [] }
    }
  ;

  // !
  document.querySelectorAll(campos.mutar.tipos).forEach(elemento => campos.mutar.funcion(elemento));

  // !
  new MutationObserver(mutaciones => mutaciones.forEach(mutacion =>
    !mutacion.target.type &&
    mutacion.addedNodes.forEach(nodo => {

      if (nodo.value === undefined)
        nodo.querySelectorAll && nodo.querySelectorAll(campos.mutar.tipos).forEach(elemento =>
          campos.mutar.funcion(elemento)
        )
      else if (nodo.type)
        campos.mutar.funcion(nodo)

    })
  )).observe(document.body, { childList: true, subtree: true });

  document.addEventListener('peticion.exitosa', function(evento) {

    if (!evento.detail.respuesta.length) {

      if  (evento.detail.argumentos.campo)
        $(evento.detail.argumentos.campo.elemento).trigger('reset')[0].click()
      ;

      return

    }

    evento.detail.respuesta = evento.detail.respuesta[0];

    if (evento.detail.respuesta.mensaje === 'PUEDES GUARDAR')
      return
    ;

    if (!evento.detail.argumentos.formulario)
      return
    ;

    evento.detail.argumentos.formulario.ventana = evento.detail.argumentos.formulario.elemento?.closest('.modal');

    if (evento.detail.argumentos.formulario.ventana) {

      evento.detail.argumentos.formulario.ventana.instancia =
        new bootstrap.Modal(evento.detail.argumentos.formulario.ventana)
      ;

      evento.detail.argumentos.formulario.ventana.instancia.hide()

    }

    if (evento.detail.respuesta.mensaje) {

      ventana.alerta = {
        instancia: new bootstrap.Modal(ventanaEmergenteAlerta),
        cabeza: ventanaEmergenteAlerta.querySelector('.modal-header'),
        parrafo: ventanaEmergenteAlerta.querySelector('.modal-body p')
      }

      ventana.alerta.parrafo.textContent = evento.detail.respuesta.mensaje;

      ventana.alerta.cabeza.classList.remove('bg-danger');
      ventana.alerta.cabeza.classList.add('bg-success');

      return ventana.alerta.instancia.show()

    }

    for (let campo in evento.detail.respuesta) {

      campo = {
        nombre: campo,
        valor: evento.detail.respuesta[campo],
        elemento: evento.detail.argumentos.formulario.elemento[campo]
      }

      if (!campo.elemento)
        continue
      ;

      if (campo.elemento.nodeName === 'SELECT')
        campo.elemento.disabled === true && !campo.elemento.options.length
          ? campo.elemento.setAttribute('value', campo.valor)
          : $(campo.elemento).val(campo.valor).change()
      else if (campo.elemento.type === 'checkbox')
        campo.elemento.checked = Number(campo.valor)
      else if (campo.elemento.dataset.mascara)
        window.aplicacion.campo.elemento.mascara[campo.elemento.id].value = campo.valor
      else {

        if (campo.valor && campo.nombre.includes('fecha'))
          campo.valor = campo.valor.split(' ')[0]
        ;

        campo.elemento.value = campo.valor

      }

    }

    if (evento.detail.respuesta.campos_deshabilitar) {

      formulario.campos = { habilitar: [] }

      evento.detail.respuesta.campos_deshabilitar.split(',').forEach((campo, iteracion) => {

        campo = {
          nombre: campo,
          elemento: evento.detail.argumentos.formulario.elemento[campo]
        }

        if (!campo.elemento)
          return
        ;

        campo.elemento.disabled = true;

        if (evento.detail.parametros.procedimiento !== 'p_traer_campos_deshabilitar')
          formulario.campos.habilitar[iteracion] = campo.elemento

      })

    }

  });

  document.addEventListener('peticion.erronea', function(evento) {

    if (!evento.detail.respuesta)
      return
    ;

    if (evento.detail.respuesta instanceof Array)
      evento.detail.respuesta = evento.detail.respuesta[0]
    else
      return evento.detail.respuesta = { mensaje: evento.detail.respuesta }
    ;

    if (evento.detail.respuesta.campo)
      window.aplicacion.peticion.formulario.campo.invalido =
        evento.detail.argumentos.formulario.elemento[evento.detail.respuesta.campo]
    ;

    if (evento.detail.respuesta.mensaje) {

      ventana.alerta = {
        instancia: new bootstrap.Modal(ventanaEmergenteAlerta),
        cabeza: ventanaEmergenteAlerta.querySelector('.modal-header'),
        parrafo: ventanaEmergenteAlerta.querySelector('.modal-body p')
      }

      ventana.alerta.parrafo.textContent = evento.detail.respuesta.mensaje;

      ventana.alerta.cabeza.classList.remove('bg-success');
      ventana.alerta.cabeza.classList.add('bg-danger');

      ventana.alerta.instancia.show()

    }

  });

  // !
  if (document.querySelector('[form]')) {

    let formulario = document.querySelector('[form]').form;

    peticion({
      parametros: { procedimiento: 'p_traer_valores_predeterminados' },
      function: {
        exitosa: respuesta => {

          if (!respuesta.length)
            return
          ;

          respuesta = respuesta[0];

          for (let indice in respuesta)
            if (formulario[indice])
              formulario[indice].dataset.campoValorPredeterminado = respuesta[indice]

        }
      },
      formulario: { elemento: formulario }
    });

    peticion.controlador = null;

    peticion({
      parametros: { procedimiento: 'p_traer_campos_deshabilitar' },
      formulario: { elemento: formulario }
    })

  }

  // {} Eventos generalizados

  // !
  $(document).on(
    `ajaxStart ajaxSend ajaxSuccess ajaxError ajaxComplete ajaxStop`,
    (evento, peticion, configuracion) => {

      // +
      switch (evento.type) {

        // ? Evento cuando no hay peticiones
        // ? en curso y se inicializa una petición
        case 'ajaxStart':
        break;

        // ? Evento antes de que una petición se envie
        case 'ajaxSend':
        break;

        // ? Evento cuando una petición se realizó
        // ? exitosamente
        case 'ajaxSuccess':

          // *
          configuracion.parametros = Object.fromEntries(parametros(configuracion.url));

          // *
          window.aplicacion.peticion.exitosa = {
            propiedades: peticion,
            configuracion: configuracion
          }

          // *
          if (!configuracion.formulario)
            return
          ;

          if (!peticion.responseJSON.length)
            return configuracion.campo && $(configuracion.campo.elemento).trigger('reset')[0].focus()
          ;

          // *
          $.each(peticion.responseJSON[0], (campo, valor) => {

            //
            campo = configuracion.formulario.elemento.querySelector(`[name=${campo}]`);

            //
            if (!campo)
              return
            ;

            //
            if (campo.nodeName === 'SELECT')
              campo.disabled === true && !campo.options.length
                ? campo.setAttribute('value', valor)
                : $(campo).val(valor).change()
            else if (campo.type === 'checkbox')
              campo.checked = Number(valor)
            else if (campo.dataset.mascara)
              window.aplicacion.campo.mascara[campo.id].value = valor
            else {

              if (valor && campo.name.includes('fecha'))
                valor = valor.split(' ')[0]
              ;

              campo.value = valor

            }

          });

          // *
          if (peticion.responseJSON[0].campos_deshabilitar) {

            //
            formulario.campos = { habilitar: [] }

            //
            peticion.responseJSON[0].campos_deshabilitar.split(',').forEach((valor, iteracion) => {

              //
              configuracion.formulario.campo =
                configuracion.formulario.elemento.querySelector(`[name=${valor.trim()}]`)
              ;

              //
              if (!configuracion.formulario.campo)
                return
              ;

              //
              configuracion.formulario.campo.disabled = true;

              //
              if (configuracion.parametros.procedimiento !== 'p_traer_campos_deshabilitar')
                formulario.campos.habilitar[iteracion] = configuracion.formulario.campo

            })

          }

        break;

        // ? Evento cuando una petición no se realizó
        // ? exitosamente
        case 'ajaxError':

        break;

        // ? Evento cuando una petición se completo
        case 'ajaxComplete':

          // *
          window.aplicacion.peticion.completada = {
            propiedades: peticion,
            configuracion: configuracion
          }

          // *
          if (!peticion.responseJSON || peticion.status > 400)
            return
          ;

          // *
          if (peticion.responseJSON[0]?.mensaje === 'PUEDES GUARDAR')
            return
          ;

          // *
          window.aplicacion.peticion.formulario.campo.invalido = $(window.aplicacion.peticion.formulario.elemento)
            .find(`[name="${peticion.responseJSON[0]?.campo}"]`)
            .addClass('is-invalid')
          ;

          // * Condición para verificar si la petición fue éxitosa
          if (peticion.status === 200)
            $(window.aplicacion.peticion.formulario.elemento).closest('.modal')?.modal('hide')
          ;

          // * Método para retornar la notificación
          // * que retorno la petición con respuesta
          // * de tipo JSON
          if (peticion.responseJSON[0]?.mensaje) {

            $(ventanaEmergenteAlerta).find('.modal-body p').text(peticion.responseJSON[0].mensaje);

            //
            $(ventanaEmergenteAlerta)
              .find('.modal-header')
              .toggleClass('bg-danger', peticion.status !== 200)
              .toggleClass('bg-success', peticion.status === 200)
            ;

            //
            $(ventanaEmergenteAlerta).modal('show')

          }

          // *
          if (document.cookie.includes('Consulta'))
            Toastify({
              text: decodeURIComponent(
                document.cookie.replace(/(?:(?:^|.*;\s*)Consulta\s*\=\s*([^;]*).*$)|^.*$/, '$1')
              ).replace(/\s+/g, ' '),
              duration: 5000,
              close: true,
              gravity: 'bottom',
              position: 'left',
              style: {
                'max-width': '100%',
                padding: '5px',
                background: 'var(--bs-dark)',
                border: '5px solid var(--bs-danger)'
              },
              onClick: function() {

                $('<textarea>')
                  .attr('id', 'consultaSQL')
                  .val(this.text)
                  .appendTo('body')
                ;

                $('textarea#consultaSQL')[0]
                  .select();

                document.execCommand('copy');

                Toastify({
                  text: '¡Consulta copiada con éxito!',
                  duration: 1000,
                  close: true,
                  position: 'left',
                  style: {
                    background: 'var(--bs-success)'
                  }
                }).showToast();

                $('textarea#consultaSQL').remove()

              }

            }).showToast()

        break

        // ? Evento cuando no se realizan más peticiones
        case 'ajaxStop':
        break;

      }

  });

  // {} Eventos generalizados con delegación

  // !
  $(document).on('change select2:selecting', 'select', function(evento) {

    let selector = {
      elemento: this,
      opciones: this.options,
      formulario: this.form
    }

    if (selector.opciones.length) {

      selector.opcion = { elemento: this.selectedOptions[0] }
      selector.opcion.campos = {
          elementos: selector.opcion.elemento?.dataset.campos?.split(','),
          valores: selector.opcion.elemento?.value
            ? selector.opcion.elemento.dataset.camposValores?.split(',')
            : [],
          habilitar: selector.opcion.elemento?.dataset.camposHabilitar?.split(','),
          deshabilitar: selector.opcion.elemento?.dataset.camposDeshabilitar?.split(',')
        }
      ;

      if (tabla.codificacion.elemento.length)
        if (selector.opciones[1]?.dataset.cuentaContableId) {

          tabla.codificacion.fila.datos = {
            id_cuenta_contable: selector.opcion.elemento.dataset.cuentaContableId,
            cuenta_contable: selector.opcion.elemento.dataset.cuentaContableDescripcion,
            debito: '',
            credito: ''
          }

          if (window.aplicacion.campo.mascara.monto)
            tabla.codificacion.fila.datos.credito = window.aplicacion.campo.mascara.monto.typedValue

        } else
          tabla.codificacion.fila.datos = {}
      ;

      if (evento.type === 'change') {

        selector.opcion.campos.elementos?.forEach((campo, iteracion) => {

          campo = {
            id: campo,
            elemento: document.querySelector(campo),
            valor: selector.opcion.campos.valores[iteracion]
          }

          if (!campo.elemento)
            campo.elemento = selector.formulario[campo.id.replace('#', '')]
          ;

          if (!campo.elemento)
            return
          ;

          campo.mascara = window.aplicacion.campo.mascara[campo.elemento.id];

          if (!campo.valor || campo.valor === undefined)
            campo.valor = campo.elemento.dataset.mascara === 'numerico' ? '0' : ''
          ;

          campo[campo.mascara ? 'mascara' : 'elemento'].value = campo.valor

        });

        selector.opcion.campos.habilitar?.forEach(campo => {

          campo = { id: campo, elemento: document.querySelector(campo) }

          if (!campo.elemento)
            campo.elemento = selector.formulario[campo.id.replace('#', '')]
          ;

          if (!campo.elemento)
            return
          ;

          campo.elemento.disabled = false

        });
        selector.opcion.campos.deshabilitar?.forEach(campo => {

          campo = { id: campo, elemento: document.querySelector(campo) }

          if (!campo.elemento)
            campo.elemento = selector.formulario[campo.id.replace('#', '')]
          ;

          if (!campo.elemento)
            return
          ;

          campo.elemento.disabled = true

        });

        if (tabla.codificacion.elemento.length && tabla.codificacion.fila.datos.id_cuenta_contable) {

          tabla.codificacion.instancia.cells(`.id:contains(${tabla.codificacion.fila.datos.id_cuenta_contable})`).nodes().each(elemento => {

            if (tabla.codificacion.fila.datos.id_cuenta_contable === elemento.textContent) {

              tabla.codificacion.fila.modificar = elemento.parentElement;

              return false

            }

          });

          tabla.codificacion.fila.insertada = tabla.codificacion.fila.modificar
            ? tabla.codificacion.instancia.row(tabla.codificacion.fila.modificar).data(tabla.codificacion.fila.datos)
            : tabla.codificacion.instancia.row.add(tabla.codificacion.fila.datos)
          ;

          tabla.codificacion.instancia.draw(false);

          tabla.codificacion.fila.modificar = null

        }

      } else {

        if (this.id === 'id_cuenta_banco' && tabla.codificacion.fila.insertada && tabla.codificacion.fila.datos.id_cuenta_contable)
          tabla.codificacion.fila.insertada?.remove().draw(false)

      }

    }

  });

  // !
  $(document).on('submit reset', 'form', function(evento, afectar) {

    // +
    if (evento.type === 'reset') {

      formulario.campos = {
        limpiar: evento.target.nodeName === 'FORM' ? [...this.elements] : [evento.target],
        habilitar: formulario.campos?.habilitar,
        prevenir: []
      }

      if (evento.target.nodeName === 'FORM') {

        formulario.campos.habilitar?.forEach(campo => campo.disabled = false);

        this.dataset.campoPrevenir?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) =>
          formulario.campos.prevenir[iteracion] = identificador.startsWith('#') ? window[identificador.slice(1)] : this[identificador]
        );

        this.dataset.campoHabilitar?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) =>
          (identificador.startsWith('#') ? window[identificador.slice(1)] : this[identificador]).disabled = false
        );

        this.dataset.campoDeshabilitar?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) =>
          (identificador.startsWith('#') ? window[identificador.slice(1)] : this[identificador]).disabled = true
        );

        campos.limpiar.funcion(
          this.dataset.campoLimpiar?.replace(/\s/g, '').split(',').map((identificador, iteracion) =>
            (identificador.startsWith('#') ? window[identificador.slice(1)] : formulario[identificador])
        ));

        this.dataset.selectorActualizar?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) => {

          let selector = {
            elemento: identificador.startsWith('#') ? window[identificador.slice(1)] : this[identificador]
          }
          selector.grupo = selector.elemento.closest('.input-group');

          window.aplicacion.campo.selector[selector.elemento.id].peticion('opciones')

        });

        this.dataset.selectorLimpiar?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) => {

          let selector = {
            elemento: identificador.startsWith('#') ? window[identificador.slice(1)] : this[identificador]
          }
          selector.grupo = selector.elemento.closest('.input-group');

          selector.elemento.innerHTML = '';
          selector.elemento.disabled = true

        });

        this.dataset.tablaLimpiarFilaActiva?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) => {

          identificador = identificador.slice(1);

          if (tabla[identificador].vacia)
            return
          ;

          tabla[identificador].fila.activa.elemento?.classList.remove('table-active')

        });

        this.dataset.tablaActualizar?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) => {

          identificador = identificador.slice(1);

          tabla[identificador].instancia.ajax.reload()

        });

        this.dataset.tablaLimpiar?.replace(/\s/g, '').split(',').forEach((identificador, iteracion) => {

          identificador = identificador.slice(1);

          tabla[identificador].instancia.clear().draw(false)

        })

      } else if (evento.target.nodeName === 'TABLE')
        return console.log(tabla[evento.target.id])
      ;

      campos.limpiar.funcion(formulario.campos.limpiar);

      return false

    }

    // +
    evento.preventDefault();

    // +
    formulario.campo = {}

    // +
    if ($(this).parent().is('.dataTables_filter')) {

      let tabla = $(this).closest('.dataTables_wrapper').find('table:has(tbody)');

      tabla.dataTable().fnSettings().ajax.data.entidad = $(this).find('input').val();

      tabla.DataTable().ajax.reload();

      return

    }

    // +
    if (!['BODY', 'DIV', 'BUTTON'].includes(document.activeElement.tagName))
      return !$(document.activeElement).closest('table').length && $(this).find('[name]:visible:enabled:not(button)').each((indice, elemento) => {

        if (elemento === document.activeElement)
          formulario.campo.actual = elemento
        else if (formulario.campo.actual) {

          elemento.focus();

          elemento.select && elemento.select();

          return false

        }

      })
    else if (!this.hasAttribute('action') && !evento.originalEvent?.submitter?.hasAttribute('formaction'))
      return
    ;

    window.aplicacion.peticion.formulario.elemento = this;

    formulario.peticion = {
      ruta: window.location.href === this.action
        ? evento.originalEvent.submitter.formAction
        : this.action,
      tipo: evento.originalEvent?.submitter.formMethod
        ? evento.originalEvent.submitter.formMethod
        : this.method
    }

    if (evento.originalEvent.submitter.formAction !== window.location.href)
      formulario.peticion.ruta = evento.originalEvent.submitter.formAction
    ;

    peticion.actual = $.ajax({
      url: formulario.peticion.ruta,
      type: formulario.peticion.tipo,
      data: parametros(window.aplicacion.peticion.formulario.parametro ?? this),
      processData: false,
      contentType: false,
      beforeSend: function(peticion, configuracion) {

        // ? Identificar si en el formulario contiene
        // ? campos inválidos y si es así entonces se
        // ? restablecerán
        $(window.aplicacion.peticion.formulario.elemento).find('.is-invalid').removeClass('is-invalid')

      },
      success: function(respuesta, estado, peticion) {

        // ?
        window.aplicacion.peticion.formulario.exitosa = {
          propiedades: peticion,
          configuracion: this
        }

        if (!respuesta)
          return
        else if (respuesta[0]?.mensaje === 'PUEDES GUARDAR') {

          evento.originalEvent.submitter.type = 'button';
          evento.originalEvent.submitter.dataset.accion = 'guardar';

          evento.originalEvent.submitter.click();

          evento.originalEvent.submitter.type = 'submit';
          delete evento.originalEvent.submitter.dataset.accion;

          return

        }

        // ?
        window.aplicacion.peticion.formulario.elemento.reset();

        // ?
        if (window.aplicacion.peticion.formulario.elemento.id.includes('formularioVentanaEmergenteCrear')) {

          if (window.aplicacion.campo.selector[ventana.emergente.campo.afectar.attr('id')]) {

            ventana.emergente.campo.afectar.attr('value', peticion.responseJSON[0].id_registro);

            window.aplicacion.campo.selector[ventana.emergente.campo.afectar.attr('id')].peticion('opciones')

          }

          if ($('script[src*="registros/plantilla.js"]').length)
            $(tablaRegistros).DataTable().ajax.reload()

        }

      },
      error: function(peticion, estado) {},
      complete: function(peticion, estado) {

        // ?
        window.aplicacion.peticion.formulario.completada.propiedades = peticion;
        window.aplicacion.peticion.formulario.completada.configuracion = this

      }
    })

  });

  // !
  window.aplicacion.ruta.includes('reportes') &&
  $('form').on('change', function(evento) {

    evento.tabla = { elemento: this.querySelector('table:has(tbody)') };

    if (!evento.tabla.elemento || evento.target.type === 'search')
      return
    ;

    $(evento.tabla.elemento).trigger('reset')

  });

	// !
	$(document).on('click', 'button[data-accion][data-accion!="crear"]', function(evento, accion = {}) {

    ventana.emergente.inicializador.ventanaEmergenteAlerta = this;

    if (!accion.tipo)
      accion.tipo = this.dataset.accion
    ;

    if (!accion.formulario)
      accion.formulario = {}
    ;

    if (!accion.ventana)
      accion.ventana = {}
    ;

    if (!accion.formulario.elemento)
      accion.formulario.elemento = $('form').has(this).length
        ? $(this).closest('form')
        : $(this.form)
    ;

    if (!accion.formulario.metodo)
      accion.formulario.metodo = this.formMethod
    ;

    if (!accion.formulario.ruta)
      accion.formulario.ruta = this.formAction
    ;

    if (!accion.ventana.elemento)
      accion.ventana.elemento = $(ventanaEmergenteAlerta)
    ;

    if (!accion.ventana.mensaje)
      accion.ventana.mensaje = `¿Estás seguro que deseas {accion}?`
    ;

    accion.ventana.boton = {
      elemento: accion.ventana.elemento.find('.modal-footer button.d-none'),
      texto: accion.tipo
    }

    accion.ventana.elemento[0].dataset.accion = accion.tipo;

    switch (accion.tipo) {

      case 'guardar': accion.ventana.boton.clases = 'btn-success'; break;

      case 'limpiar': accion.ventana.boton.clases = 'btn-warning'; break;

      case 'eliminar': accion.ventana.boton.clases = 'btn-danger'; break;

    }

    if (accion.funcion) {

      accion.ventana.boton.elemento.attr('type', 'button');

      accion.ventana.boton.elemento[0].onclick = accion.funcion

    } else {

      if (accion.tipo === 'guardar' && accion.formulario.elemento.find('div.d-none:first input[data-mascara=entero]').val())
        accion.ventana.boton.texto = 'modificar'
      ;

      if (accion.formulario.metodo === '')
        accion.formulario.metodo = accion.formulario.elemento.attr('method')
      ;

      if (accion.formulario.ruta === location.href)
        accion.formulario.ruta = accion.formulario.elemento.attr('action')
      ;

      accion.ventana.boton.elemento.attr({
        type: accion.tipo === 'guardar' ? 'submit' : 'reset',
        form: accion.formulario.elemento.attr('id'),
        formmethod: accion.formulario.metodo,
        formaction: `${accion.formulario.ruta}&renglon=${this.dataset.accion}`
      })

    }

    accion.ventana.boton.elemento.addClass(accion.ventana.boton.clases).text(accion.ventana.boton.texto);

    accion.ventana.elemento.find('.modal-content .subtitle').text(accion.ventana.mensaje.replace('{accion}', accion.tipo));

		$(ventanaEmergenteAlerta).modal('show')

	});

  // !
  $(document).on('click', '[data-accion="crear"]', async function(evento, accion = {}) {

		accion = {
			programa: {
				generico: accion.programa?.generico ?? this.dataset.programaGenerico
			},
			peticion: {
				ruta: accion.peticion?.ruta ?? this.dataset.peticionRuta,
				parametros: accion.peticion?.parametros ?? this.dataset.peticionParametros,
				tabla: accion.peticion?.tabla ?? this.dataset.peticionTabla,
				campo: accion.peticion?.campo ?? this.dataset.peticionCampo,
				id: accion.peticion?.id ?? this.dataset.peticionId,
				procedimiento: {
					registrar: accion.peticion?.procedimiento?.registrar ?? this.dataset.peticionProcedimientoRegistrar
				}
			}
		}

    if (!accion.peticion.campo)
      return
    ;

		if (!accion.peticion.tabla)
			accion.peticion.tabla = pluralizarFrase(accion.peticion.campo.replace('id_', ''))
		;

		accion.programa.actual = `registros.${accion.peticion.tabla?.replaceAll('_', '')}`;

		if (!accion.programa.generico)
			accion.programa.generico = $('script[src*="registros/plantilla.js"]').length
      ? window.aplicacion.programa.generico
      : window.aplicacion.programa.genericos.includes(accion.programa.actual)
		;

		// accion.programa.generico = JSON.parse(accion.programa.generico);

		if (!accion.peticion.ruta)
			accion.peticion.ruta = accion.programa.generico ? 'esquema' : 'ejecutar'
		;

		if (!accion.peticion.parametros)
			accion.peticion.parametros = accion.programa.generico
				? { tabla: accion.peticion.tabla }
				: { procedimiento: 'p_traer_campos_registros', programa: accion.programa.actual }
		;

		if (!accion.peticion.procedimiento.registrar)
			accion.peticion.procedimiento.registrar = `p_registrar_${accion.peticion.tabla}`
		;

		accion.ventana = {
			variable: capitalizarLetra(accion.peticion.tabla, '_').replaceAll('_', ''),
			titulo: accion.peticion.id ? 'Modificación' : 'Creación'
		}

    ventana.emergente.campo = { afectar: $(this).closest('div').find('select') }

    accion.ventana.elemento = $(`
      <div class="modal fade"
        id="ventanaEmergenteCrear${accion.ventana.variable}"
        tabindex="-1"
        aria-hidden="true"
        data-bs-backdrop="static"
        data-bs-keyboard="false"
        data-bs-focus="false"
      >
        <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
          <div class="modal-content">

            <div class="modal-header bg-success">
              <h1 class="modal-title fs-5">
                ${accion.ventana.titulo} de ${capitalizarLetra(accion.peticion.campo.replace('id_', '').replaceAll('_', ' '))}
              </h1>
              <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>

            <div class="modal-body">
            </div>

            <div class="modal-footer justify-content-between">
              <button type="submit"
                class="btn btn-success text-capitalize"
                form="formularioVentanaEmergenteCrear${accion.ventana.variable}"
                formmethod="POST"
                formaction="${window.aplicacion.enlace}/ejecutar?procedimiento=${accion.peticion.procedimiento.registrar}"
              >${ accion.peticion.id ? 'Modificar' : 'Guardar' }</button>
              <button type="button" class="btn btn-secondary text-capitalize" data-bs-dismiss="modal">Cerrar</button>
            </div>

          </div>
        </div>
      </div>
    `);

		ventana.emergente.id = accion.ventana.elemento.attr('id');
		ventana.emergente.inicializador[ventana.emergente.id] = this;

    accion.ventana.formulario = $(`
      <form class="form" id="formularioVentanaEmergenteCrear${accion.ventana.variable}" autocomplete="off">
        <div class="row">
        </div>
      </form>
    `).prepend($('[name=_token]').first().clone());

    accion.ventana.elemento.find('.modal-body').append(accion.ventana.formulario);

    // +
    if (accion.peticion.id)
      await $.ajax({
        url: `${window.aplicacion.enlace}/ejecutar`,
        type: 'GET',
        data: {
          procedimiento: 'p_traer_registros_consulta_principal',
          origen_registros: accion.peticion.tabla,
          sql: `${accion.peticion.campo} = ${accion.peticion.id}`
        },
        beforeSend: function(peticion, configuracion) {},
        success: (respuesta, estado, peticion) => {

          accion.ventana.campo = respuesta[0]

        },
        error: function(peticion, estado) {},
        complete: (peticion, estado) => {}
      })
    ;

    // +
    await $.ajax({
      url: `${window.aplicacion.enlace}/${accion.peticion.ruta}`,
      type: 'GET',
      data: accion.peticion.parametros,
      beforeSend: function(peticion, configuracion) {},
      success: (respuesta, estado, peticion) => {

        respuesta.forEach((registro, iteracion) => {

          if (['id_usuario', 'fecha_registro', 'fecha_actualizado', 'id_estado'].includes(registro.nombre))
            return
          ;

					if (registro.id) {

						if (registro.tipo === 'select')
							registro.elemento = 'select'

					} else {

            registro.id = registro.nombre;

            // registro.valor = registro.valor_predeterminado?.replace(/\(|\)|\'/g, '') ?? '';

						registro.valor = '';

            registro.titulo = capitalizarLetra(registro.id.replace('id_', '').replaceAll('_', ' '));

            registro.visible = registro.id !== accion.peticion.campo;

            if (registro.visible && registro.id.startsWith('id_')) {

              registro.elemento = 'select';

              registro.tabla = pluralizarFrase(registro.titulo.replaceAll(' ', '_').toLowerCase())

            }

          }

          if (!registro.ancho)
            registro.ancho = 'col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 col-xxl-12'
          ;

          if (accion.ventana.campo && accion.ventana.campo[registro.id])
            registro.valor = accion.ventana.campo[registro.id]
          ;

          if (typeof registro.visible === 'string')
            registro.visible = Number(registro.visible)
          ;

          if (registro.tabla || registro.procedimiento)
            registro.foranea = true
          ;

          registro.elemento = $(registro.elemento === 'select' || registro.tipo === 'bit' ? '<select></select>' : '<input>');

          registro.elemento.prop(function() {

            let propiedades = { name: registro.id };

            if (registro.elemento.is('select')) {

              registro.elemento.attr('value', registro.valor);

              propiedades.class = 'form-select';

              if (registro.tipo === 'bit')
                registro.elemento.append(`<option value="0">No</option><option value="1">Si</option>`)
              else if (registro.tabla)
                registro.elemento[0].dataset.peticionTabla = registro.tabla
              else {

                registro.elemento[0].dataset.peticionProcedimiento = registro.procedimiento;

                if (registro.parametros)
                  registro.elemento[0].dataset.peticionParametros = registro.parametros

              }

            } else {

              if (registro.tipo === 'datetime') {

                propiedades.type = 'date';

                registro.valor = registro.valor.split(' ')[0];

              } else
                propiedades.type = 'text'
              ;

              registro.elemento.val(registro.valor);

              propiedades.class = 'form-control';

              if (registro.tipo === 'int' || /telefono|celular|whatsapp|cedula|rnc|identificacion/i.test(registro.id)) {

                propiedades.inputMode = 'numeric';

                if (/telefono|celular|whatsapp/.test(registro.id))
                  registro.elemento[0].dataset.mascara = 'telefono'
                else if (/cedula|rnc|identificacion/.test(registro.id))
                  registro.elemento[0].dataset.mascara = 'cedula'
                else
                  registro.elemento[0].dataset.mascara = 'entero'

              } else if (registro.tipo === 'numeric' || registro.id === 'pasaporte') {

                propiedades.inputMode = 'decimal';
                registro.elemento[0].dataset.mascara = registro.tipo === 'numeric' ? 'numerico' : registro.tipo

              }

            }

            propiedades.title = registro.titulo;

            return propiedades

          }());

          registro.contenedor = `
            <div class="form-group">
              <label class="text-capitalize" for="${registro.id}">${registro.titulo}</label>
            </div>
          `;

          if (registro.foranea)
            registro.contenedor = `
              <div class=input-group>
                ${registro.contenedor}
                <button type=button class="btn btn-success" data-accion=crear data-peticion-campo=${registro.id}>
                  <i class="fa-solid fa-plus"></i>
                </button>
              </div>
            `
          ;

          registro.contenedor = `<div class="${registro.ancho}">${registro.contenedor}</div>`;

          registro.contenedor = $(registro.contenedor);

          if (!registro.visible)
            registro.contenedor.addClass('d-none')
          ;

          registro.elemento.insertAfter(registro.contenedor.find('label'));

          accion.ventana.formulario.find('.row').append(registro.contenedor);

        });

        $(accion.ventana.elemento).insertBefore('#ventanaEmergenteAlerta')

      },
      error: function(peticion, estado) {},
      complete: (peticion, estado) => {}
    });

		accion.ventana.elemento.modal('show')

  });

  // !
  $(document).on('keydown', '.dataTables_filter', function(evento) {

    let tabla = { elemento: $(this).closest('.dataTables_wrapper').find('table:has(tbody)') }

    // tecla = {
    //   flecha: {
    //     izquierda: evento.which === 37,
    //     arriba: evento.which === 38,
    //     derecha: evento.which === 39,
    //     abajo: evento.which === 40
    //   }
    // }

    if ((evento.which !== 38 && evento.which !== 40) || tabla.elemento.find('td.table-active').length)
      return
    ;

    tabla.fila = { activa: tabla.elemento.find('tr.table-active') }

    tabla.columna = {
      activar: tabla.fila.activa.length
        ? tabla.fila.activa.find('td:first')
        : tabla.elemento.find('tbody tr:not(.dtrg-group):first').find('td:not(.dataTables_empty):first')
    }

    document.activeElement.blur();

    tabla.columna.activar.trigger('click')

  });

  // {} Eventos generalizados con delegación de librerías externas

  // !
  $(document).on('show.bs.modal', '.modal', function(evento) {

    ventana.emergente.elemento = this;

    ventana.emergente.formulario = {
      elemento: $(this).find('form')
    }

    ventana.emergente.tabla = {
      elemento: $(this).find('table:has(tbody)'),
      instanciada: function() { return this.elemento.is('.dataTable') }
    }

    switch (this.id) {

      case 'ventanaEmergenteConsulta': break;

      case 'ventanaEmergenteAlerta': break;

    }

    if (this.id.includes('ventanaEmergenteCrear'))
      ventana.emergente.inicializador[ventana.emergente.id].disabled = true

  });

  // !
  $(document).on('shown.bs.modal', '.modal', function(evento) {

    $(document).trigger(jQuery.Event('keydown', { keyCode: 27 }));

    switch (this.id) {

      case 'ventanaEmergenteConsulta':

        if(ventana.emergente.tabla.instanciada())
          ventana.emergente.formulario.elemento.submit()
        else
          new Tabla({
            tabla: ventana.emergente.tabla.elemento,
            columna: {
              peticion: {
                ruta: `${window.aplicacion.enlace}/ejecutar`,
                parametro: {
                  procedimiento: 'p_traer_encabezado_consultas',
                  renglon: ventana.emergente.inicializador[ventana.emergente.id].dataset.peticionCampo
                }
              }
            },
            datos: {
              peticion: {
                ruta: `${window.aplicacion.enlace}/ejecutar`,
                parametro: {
                  procedimiento: ventana.emergente.inicializador[ventana.emergente.id].dataset.peticionProcedimiento ?? 'p_traer_entidades',
                  renglon: ventana.emergente.inicializador[ventana.emergente.id].dataset.peticionTabla,
                  filtro: ventana.emergente.inicializador[ventana.emergente.id].dataset.peticionFiltro ?? '',
                  entidad: ''
                }
              }
            },
            configuracion: {
              campo: {
                busqueda: { peticion: true }
              },
              dom: `ft`
            }
          }).inicializar()

      break;

      case 'ventanaEmergenteAlerta': break;

    }

    let enfocar = $(this).find('.modal-body [name]:visible:first');

		if (!enfocar.length)
			enfocar = $(this).find('.modal-body :input:visible:first')
		;

		enfocar.focus().click();

    ventana.emergente.tabla.elemento.each((indice, elemento) =>
      tabla[elemento.id] &&
      tabla[elemento.id].instancia.columns.adjust()
    )

  });

  // !
  $(document).on('hide.bs.modal', '.modal', function(evento) {

    if (ventana.emergente.tabla.elemento.length && !ventana.emergente.tabla.elemento.is('.dataTable'))
      return false
    ;

    switch (this.id) {

      case 'ventanaEmergenteConsulta':

        // $('.dataTables_scrollBody .dataTable', this)
        //   .DataTable()
        //   .clear()
        //   .draw()

      break;

      case 'ventanaEmergenteAlerta': break;

    }

  });

  // !
  $(document).on('hidden.bs.modal', '.modal', function(evento) {

    if (!ventana.emergente.campo)
      ventana.emergente.campo = {}
    ;

    switch (this.id) {

      case 'ventanaEmergenteConsulta':

        ventana.emergente.formulario.elemento.trigger('reset');
        ventana.emergente.formulario.elemento.find('[type=search]').val(null).trigger('input');

        ventana.emergente.tabla.elemento.DataTable().destroy();

        ventana.emergente.campo.afectar
          .closest('form')
          .find('[name]:visible:enabled:not(button)')
          .add(ventana.emergente.campo.afectar)
          .toArray()
          .forEach((campo, iteracion, campos) => {

            if (campo === ventana.emergente.campo.afectar[0]) {

              ventana.emergente.campo.enfocar = campos[iteracion + 1];

              if (!ventana.emergente.campo.enfocar)
                ventana.emergente.campo.enfocar = ventana.emergente.campo.afectar[0]
              ;

              return false

            }

          })

      break;

      case 'ventanaEmergenteAlerta':

				if (this.dataset.accion) {

					let boton = $(this).find('.modal-footer button.d-none');

					if (boton[0]?.classList.contains('btn-success'))
						boton.removeClass('btn-success')
					else if (boton[0]?.classList.contains('btn-warning'))
						boton.removeClass('btn-warning')
          else
            boton.removeClass('btn-danger')
          ;

					delete this.dataset.accion

				} else {

          // Condición para identificar si la petición
          // retorno en la respuesta de tipo JSON un
          // campo a validar y si es así entonces se
          // encofará en ese campo, y se le añadirá una
          // clase '.is-invalid'
          $(`.list-group-item[href="#${$(window.aplicacion.peticion.formulario.campo.invalido).closest('.tab-pane').attr('id')}"]`).tab('show');

          if (window.aplicacion.peticion.formulario.campo.invalido.length) {

            ventana.emergente.campo.enfocar = window.aplicacion.peticion.formulario.campo.invalido;

            window.aplicacion.peticion.formulario.campo.invalido = $()

          } else
            ventana.emergente.campo.enfocar = ventana.emergente.campo.afectar

        }

      break

      default:

        if (this.id.includes('ventanaEmergenteCrear')) {

          ventana.emergente.inicializador[this.id].disabled = false;

          $(this).remove();

          return false

        }

      break

    }

    // if (!ventana.emergente.campo.enfocar)
    //   ventana.emergente.campo.enfocar = $(ventana.emergente.inicializador[this.id]).closest('form').find('[name]:visible:enabled:not(button)').first()
    // ;

    setTimeout(() => {

      $(ventana.emergente.campo.enfocar).focus();

      ventana.emergente.campo.enfocar = null

    }, 1)

  });

  // {} Eventos

  // !
  $('[data-bs-target]').click(function(evento) {

		ventana.emergente.id = this.dataset.bsTarget.replace('#', '');

    ventana.emergente.inicializador[ventana.emergente.id] = this;

    ventana.emergente.campo = { afectar: $(this).closest('.input-group').find('input[id*=id_]') }

    $('#tituloVentanaEmergenteConsulta').text(`Consulta de ${
      this.dataset.ventanaEmergenteTitulo ??
      pluralizarFrase(ventana.emergente.inicializador[ventana.emergente.id].dataset.peticionCampo)
    }`);

    if (this !== ventana.emergente.inicializador[ventana.emergente.id]) {

      if (ventana.emergente.id === 'ventanaEmergenteConsulta') {

        if ($.fn.DataTable.isDataTable($('#tablaRegistrosConsulta')))
          $('#ventanaEmergenteConsulta #tablaRegistrosConsulta').DataTable().destroy()

      }

    }

  });

  // !
  $('table thead [type="checkbox"]').click(function(evento) {

    console.log(this)

  });

  // !
  $('.input-group button:has(.fa-solid.fa-rotate)').click(function(evento) {

    window.aplicacion.campo.selector[
      $(this).parent().find('select').attr('id')
    ].peticion('opciones')

  });

  // !
  $('.input-group:has([data-bs-toggle="modal"]) input').on('keydown', function(evento) {

    if (evento.key !== 'Enter')
      return
    ;

    this.value
      ? this.blur()
      : this.closest('.input-group').querySelector('[data-bs-target]').click()

  });

  // !
  $('body > header .navbar.navbar-expand-lg .offcanvas .favorito').click(function(evento) {

    let programa = { id: 0, favorito: false }

    if ($(this).siblings().is('.dropdown-item'))
      programa = {
        id: $(this).siblings().data().programaId,
        favorito: $(this).siblings().data().hasOwnProperty('programaFavorito'),
      }

    $(this).toggleClass('text-warning opacity-lg-0');

    peticion.actual = $.ajax({
      url: `${window.aplicacion.enlace}/ejecutar`,
      data: {
        procedimiento: 'p_registrar_programas_favoritos',
        id_programa: programa.id,
        id_usuario_asignar: aplicacion.sesion.id_usuario,
        accion: programa.favorito ? 'QUITAR' : ''
      },
      beforeSend: function(peticion, configuracion) {},
      success: function(respuesta, estado, peticion) {},
      error: function(peticion, estado) {},
      complete: function(peticion, estado) {}
    });

    // $(temp1).prev().attr('href').split('daite/')[1].replace('/', '.')

  });

  // !
  $('#ventanaEmergenteConsulta .modal-footer .btn-primary').click(function(evento) {

    $('#tablaRegistrosConsulta tr.table-active').dblclick()

  });

  // {} Eventos delegados

  // !
  $('table thead').on('click', '[type="checkbox"]', function(evento) {

    if ($(this).closest('table').is('.dataTable'))
      $(this)
        .closest('.dataTables_scroll')
        .find(`.dataTables_scrollBody tbody td:nth-child(${$(this).parent().index() + 1}) [type="checkbox"]:enabled`)
        .prop('checked', !this.checked)
        .click()
    else
      $(this)
        .closest('table')
        .find(`tbody td:nth-child(${$(this).parent().index() + 1}) [type="checkbox"]:enabled`)
        .prop('checked', !this.checked)
        .click()

  });

  // !
  $('table tbody').on('click', 'tr', function(evento) {

		// +
		if (!$(evento.target).closest('.eliminar').length)
			return
		;

		// +
		$(botonAccion).trigger('click', {
			tipo: 'eliminar',
			funcion: () => $(evento.delegateTarget).closest('table').DataTable().row(this).remove().draw(false)
		})

  });

  // !
  $('table[data-tabla-seleccionable] tbody').on('click dblclick', 'tr', function(evento) {

    // +
    let seleccion = this.closest('table').dataset.tablaSeleccionable;

    if (
      $(this).has('.dataTables_empty').length ||
      $(this).is('.dtrg-group') ||
      (evento.type === 'click' && seleccion === 'doble')
    )
      return
    ;

    if (seleccion !== 'multiple' && !$(this).is('.table-active'))
      $(this).parent().find('tr.table-active').removeClass('table-active')
    ;

    this.classList.add('table-active')

  });

  // !
  $('#tablaRegistrosConsulta tbody').on('dblclick', 'tr', function(evento) {

    // +
    if ($(this).has('.dataTables_empty').length)
      return
    ;

    $(this).addClass('table-active');

    // +
    ventana.emergente.campo.afectar.val($(evento.delegateTarget).find('.table-active .id').text()).focusout().change();

    // +
    $('#ventanaEmergenteConsulta').modal('hide')

  });

  // {} Eventos de librerías externas

  // !
  $('table').on('init.dt draw.dt', function(evento) {

		if (!this.classList.contains('dataTable'))
			return
		;

    evento.tabla = { id: this.id, elemento: this, instancia: new DataTable(this) }
    evento.tabla.componente = { instancia: evento.tabla.instancia.table() }
    evento.tabla.contenedor = evento.tabla.componente.instancia.container();
    evento.tabla.cabeza = evento.tabla.componente.instancia.header();
    evento.tabla.pie = evento.tabla.componente.instancia.footer();

    evento.tabla.columna = {}
    evento.tabla.instancia.columns().every(function(columna) {

      columna = {
        indice: columna,
        instancia: this,
        cabeza: this.header(),
        pie: { elemento: this.footer() }
      }
      columna.pie.totalizar = columna.cabeza.dataset.columnaSumar === '';

      if (columna.pie.totalizar)
        columna.pie.total = () => sumar(columna.instancia.data())
      ;

      evento.tabla.columna[columna.cabeza.dataset.columnaNombre] = columna

    });

    evento.tabla.campo = {
      busqueda: evento.tabla.contenedor.querySelector('.dataTables_filter input')
    }

    evento.tabla.formulario = { elemento: evento.tabla.contenedor.closest('form') }

    if (evento.tabla.formulario.elemento) {

      evento.tabla.formulario.campo = {
        primario: {
          elemento: evento.tabla.formulario.elemento[
            evento.tabla.cabeza.querySelector('th.id')?.dataset.columnaNombre
          ]
        }
      }

      if (evento.tabla.formulario.campo.primario.elemento)
        evento.tabla.formulario.campo.primario.valor = evento.tabla.formulario.campo.primario.elemento.value

    }

    if (evento.type === 'init') {

      if (evento.tabla.id === 'tablaRegistrosCodificacion')
        return tabla.codificacion.instancia = evento.tabla.instancia
      ;

    }

    evento.tabla.vacia = !!evento.tabla.elemento.querySelector('td.dataTables_empty');

    evento.tabla.cabeza.querySelectorAll('input').forEach(campo => campo.disabled = true);

    if (!evento.tabla.vacia) {

      evento.tabla.fila = {
        activa: { instancia: evento.tabla.instancia.row('.table-active') }
      }
      evento.tabla.fila.activa.elemento = evento.tabla.fila.activa.instancia.node();

      if (evento.tabla.formulario.elemento && evento.tabla.formulario.campo.primario.elemento) {

        evento.tabla.fila.activa.elemento &&
        evento.tabla.fila.activa.elemento.classList.remove('table-active');

        evento.tabla.instancia.cells(`.id:contains(${evento.tabla.formulario.campo.primario.valor})`).nodes().each(celda =>
          celda.textContent === evento.tabla.formulario.campo.primario.valor &&
          celda.parentElement.classList.add('table-active')
        )

      }

    }

    tabla[evento.tabla.id] = evento.tabla

  });

  // !
  $('table').on('key', function(evento, configuracion, tecla, celda, disparador) {

    celda = { instancia: celda, elemento: celda.node() }
    celda.campo = celda.elemento.querySelector('input');

    if (celda.campo && celda.campo !== document.activeElement) {

      disparador.key === 'Backspace' &&
      celda.campo.select && celda.campo.select();

      if (
        celda.campo.dataset.mascara &&
        disparador.key >= '0' &&
        disparador.key <= '9'
      ) {

        celda.campo.select();

        window.aplicacion.campo.mascara[celda.campo.id].value = '';

      } else if (celda.campo.type === 'checkbox' && tecla === 13 || tecla === 32)
        celda.campo.click()

    } else if (tecla === 13)
      return $(this).find('tbody tr:has(.table-active)').trigger('dblclick')

  });

  // !
  $('table').on('key-prefocus key-focus', function(evento, configuracion, celda, disparador) {
// console.log(evento, configuracion, celda, disparador);
    let
      tecla = {
        flecha: {
          izquierda: disparador.which === 37,
          arriba: disparador.which === 38,
          derecha: disparador.which === 39,
          abajo: disparador.which === 40
        }
      }

    if (evento.type === 'key-prefocus') {

      // console.log(evento, celda, disparador);

      // disparador.type === 'draw' &&
      // celda.row().node().classList.add('table-active')

    }

    if (evento.type === 'key-focus') {

      !this.dataset.tablaSeleccionable &&
      $(celda.node()).trigger('click');

      // disparador.type !== 'click' &&
      // $('.dataTables_scrollBody').scrollTop(celda.node().offsetTop - $('.dataTables_scrollBody').offset().top / 2)

      // $(this).closest('.dataTables_scrollBody').scrollTop(celda.node().offsetTop)

      // console.log($(celda.node()).parent().prev())

    }

  });

  $('table').on('key-blur', function(evento, configuracion, celda) {

    celda.campo = $(celda.node()).has('input');

    if (celda.campo.length) {

      if ($(document.activeElement).closest('td').length)
        document.activeElement.blur()

    }

  });

  // !
  $('a[data-bs-toggle="list"]').on('shown.bs.tab', function(evento) {

    $(`${this.getAttribute('href')}.tab-pane.show`)
      .has('table.dataTable')
      .find('table:has(tbody)')
      .DataTable()
      .columns
      .adjust()
      .draw(false)

  });

  // !
  $('#ventanaEmergenteConsulta #tablaRegistrosConsulta').on('init.dt xhr.dt', function(evento) {

    $('.dataTables_wrapper').has(this).find('.dataTables_filter input').focus()

  });

  // {} Eventos delegados con librerías externas

  // !
  $('table tbody').on('change', 'tr', function(evento) {

    evento.tabla = tabla[evento.delegateTarget.parentElement.id];

    evento.tabla.fila.datos = evento.tabla.instancia.row(this).data();

    evento.tabla.celda = {
      elemento: evento.target.closest('td'),
      valor: {
        asignar: Number(evento.target.value.replaceAll(',', '')),
        maximo: Number(
          evento.tabla.instancia
            .cell(this._DT_RowIndex, `[data-columna-nombre=${evento.target.dataset.columnaValorMaximo}]`)
            .data()
        )
      },
      afectar: { nombre: evento.target.dataset.columnaAfectarNombre }
    }
    evento.tabla.celda.nombre =
      evento.tabla.instancia.column(evento.tabla.celda.elemento).header().dataset.columnaNombre
    ;
    evento.tabla.celda.indice = evento.tabla.celda.elemento.cellIndex;

    if (evento.tabla.celda.valor.maximo < evento.tabla.celda.valor.asignar)
      evento.tabla.celda.valor.asignar = evento.tabla.celda.valor.maximo
    ;

    evento.tabla.celda.instancia = evento.tabla.instancia
      .cell(this._DT_RowIndex, `[data-columna-nombre=${evento.tabla.celda.nombre}]`)
    ;

    evento.tabla.celda.instancia.data(evento.tabla.celda.valor.asignar.toString());

    if (evento.tabla.columna[evento.tabla.celda.nombre].pie.totalizar)
      evento.tabla.columna[evento.tabla.celda.nombre].pie.elemento.textContent = formatoNumerico(
        evento.tabla.columna[evento.tabla.celda.nombre].pie.total(),
      2)
    ;

    if (evento.tabla.columna[evento.tabla.celda.afectar.nombre]) {

      evento.tabla.celda.afectar.instancia = evento.tabla.instancia
        .cell(this._DT_RowIndex, `[data-columna-nombre=${evento.tabla.celda.afectar.nombre}]`)
      ;
      evento.tabla.celda.afectar.valor = {
        actual: Number(evento.tabla.celda.afectar.instancia.data()),
        asignar: Number(
          evento.tabla.instancia
            .cell(this._DT_RowIndex, `[data-columna-nombre=${evento.target.dataset.columnaAfectarValor}]`)
            .data()
        )
      }

      if (isNaN(evento.tabla.celda.afectar.valor.asignar))
        evento.tabla.celda.afectar.valor.asignar = evento.tabla.celda.valor.asignar ? 1 : 0
      else if (!evento.tabla.celda.valor.asignar)
        evento.tabla.celda.afectar.valor.asignar = 0
      ;

      evento.tabla.celda.afectar.instancia.data(evento.tabla.celda.afectar.valor.asignar.toString());

      if (evento.tabla.columna[evento.tabla.celda.afectar.nombre].pie.totalizar)
        evento.tabla.columna[evento.tabla.celda.afectar.nombre].pie.elemento.textContent = formatoNumerico(
          evento.tabla.columna[evento.tabla.celda.afectar.nombre].pie.total(),
        2)

    }

    // evento.tabla.elemento.trigger('draw')

  })

})()
