Herramientas Informaticas

Etiqueta: GNU/LINUX Página 9 de 12

Creando catalogo de Empresas #03

Ya tenemos nuestros menús creados ahora sigue meter los catálogos de configuración, el primero seria el catalogo de empresas en el cual se van a capturar los datos de la empresa como nombre, razón social, domicilio logo y datos de facturación del SAT, estos datos saldrán en las impresiones de cotizaciones, ventas y reportes.

Primero para hacer los datatables mas fácil instalamos el paquete de hermawan/codeigniter4-datatables esto solo lo haremos una vez y nos servira para los demás catálogos para instalarlo corremos el siguiente código en nuestro proyecto

composer require hermawan/codeigniter4-datatables

Luego en app/config/autoload.php en la variable $psr4 agregamos el namespace

    public $psr4 = [
        APP_NAMESPACE => APPPATH, // For custom app namespace
        'Config'      => APPPATH . 'Config',
        'Hermawan\DataTables'   => APPPATH .'ThirdParty/codeigniter4-datatables/src', // <-- namespace for this library
    ];

Ahora para obtener todos los catálogos del SAT instalamos el paquete de phpcfdi/sat-catalogos

composer require phpcfdi/sat-catalogos

El archivo de app/controller/BaseController.php quedaria de la siguiente forma para poder utilizar los catalogos del SAT en todo momento.

<?php

namespace App\Controllers;

use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
use PhpCfdi\SatCatalogos\Factory;

/**
 * Class BaseController
 *
 * BaseController provides a convenient place for loading components
 * and performing functions that are needed by all your controllers.
 * Extend this class in any new controllers:
 *     class Home extends BaseController
 *
 * For security be sure to declare any new methods as protected or private.
 */


abstract class BaseController extends Controller
{
    /**
     * Instance of the main Request object.
     *
     * @var CLIRequest|IncomingRequest
     */
    protected $request;

    /**
     * An array of helpers to be loaded automatically upon
     * class instantiation. These helpers will be available
     * to all other controllers that extend BaseController.
     *
     * @var array
     */
    protected $helpers = [];
    
    
    public $catalogosSAT;

    /**
     * Constructor.
     */
    public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
    {
        // Do Not Edit This Line
        parent::initController($request, $response, $logger);

        // Preload any models, libraries, etc, here.

        // E.g.: $this->session = \Config\Services::session();
        
          
        $dsn = "sqlite:".ROOTPATH."writable\database\catalogossat.db";
        $factory = new Factory();
        $satCatalogos = $factory->catalogosFromDsn($dsn);
        $this->catalogosSAT = $satCatalogos;
        
        
    }
}

Si se dan cuenta nosotros tenemos un archivo en writable/database/catalogossat.db para generar el archivo de base de datos SQLITE con los datos que proporciona el SAT tenemos el siguiente script en PHP, cortesía de nuestro amigo Rene

<?php

if(file_exists ( 'sat.db' )){
	
	unlink('sat.db' );
	
}
$sat = new SQLite3('sat.db');

$list = "https://raw.githubusercontent.com/phpcfdi/resources-sat-catalogs/master/database/tables.list";
$schema = "https://raw.githubusercontent.com/phpcfdi/resources-sat-catalogs/master/database/schemas/";
$data = "https://raw.githubusercontent.com/phpcfdi/resources-sat-catalogs/master/database/data/";

$listaText = file_get_contents($list);
$lines = array_filter(array_map('trim',explode("\n", $listaText)));

foreach ($lines as $line) {
    echo "\x1b[33m" . $line . "\x1b[0m \n";

    $schemaText = file_get_contents($schema . $line . ".sql");
    echo $schemaText . "\n";
    $sat->exec($schemaText);

    $dataText = file_get_contents($data . $line . ".sql");
    $dataLines = array_filter(array_map('trim',explode("\n", $dataText)));
    foreach ($dataLines as $dataLine) {
        echo "\x1b[2m" . $dataLine . "\x1b[0m \n";
        $sat->exec($dataLine);
    }

    echo "\x1b[32m data done\n \x1b[0m \n";
}

Ahora si los archivos del catalogo, creamos el archivo app/controller/EmpresasController.php con el siguiente contenido

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use \App\Models\EmpresasModel;
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;

class EmpresasController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $empresas;

    public function __construct() {
        $this->empresas = new EmpresasModel();
        $this->log = new LogModel();
        helper('menu', 'filesystem');
    }

    public function index() {


        if ($this->request->isAJAX()) {




            $datos = $this->empresas->select('id,nombre,direccion,rfc,telefono,correoElectronico,'
                            . 'diasEntrega,caja,logo,certificado,archivoKey,contraCertificado,regimenFiscal,razonSocial,codigoPostal,'
                            . 'CURP,created_at,updated_at')->where('deleted_at', null);

            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }



        $regimenesFiscales = $this->catalogosSAT->regimenesFiscales40()->searchByField("texto", "%%", 1000);
        


        $titulos["title"] = lang('empresas.title');
        $titulos["subtitle"] = lang('empresas.subtitle');
        $titulos["regimenesFiscales"] = $regimenesFiscales;

        //$data["data"] = $datos;
        return view('empresas', $titulos);
    }

    /**
     * Read Vehicle
     */
    public function obtenerEmpresa() {


        $idEmpresa = $this->request->getPost("idEmpresa");
        $datosEmpresa = $this->empresas->find($idEmpresa);

        echo json_encode($datosEmpresa);
    }

    /**
     * Save or update Vehicle
     */
    public function save() {


        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;

        $datos = $this->request->getPost();

        unset($datos["certificado"]);
        unset($datos["archivoKey"]);
        unset($datos["logo"]);

        $certificado = $this->request->getFile('certificado');
        $archivoKey = $this->request->getFile('archivoKey');
        $logo = $this->request->getFile('logo');

        if ($certificado <> null) {
            if ($certificado->getClientExtension() <> "cer") {

                return lang("empresas.certExtensionIncorrect");
            }

            $datos["certificado"] = $datos["rfc"] . "_certificado.cer";
        }

        if ($archivoKey <> null) {
            if ($archivoKey->getClientExtension() <> "key") {

                return lang("empresas.keyFileExtensionIncorrect");
            }

            $datos["archivoKey"] = $datos["rfc"] . "_certificado.key";
        }

        if ($logo) {

            if ($logo->getClientExtension() <> "png") {

                return lang("empresas.pngFileExtensionIncorrect");
            }
            $datos["logo"] = $datos["rfc"] . "_logo.png";
        }

        if ($datos["idEmpresa"] == 0) {


            try {
                
                if ($this->empresas->save($datos) === false) {

                    $errores = $this->empresas->errors();

                    foreach ($errores as $field => $error) {

                        echo $error . " ";
                    }

                    return;
                }

                $dateLog["description"] = lang("empresas.logDescription") . json_encode($datos);
                $dateLog["user"] = $userName;

                $this->log->save($dateLog);

                if ($certificado <> null) {
                    $certificado->move(WRITEPATH . "uploads\certificates", $datos["rfc"] . "_certificado.cer");
                }

                if ($archivoKey <> null) {
                    $archivoKey->move(WRITEPATH . "uploads\certificates", $datos["rfc"] . "_certificado.key");
                }

                if ($logo <> null) {
                    $logo->move("images\logo", $datos["rfc"] . "_logo.png");
                }

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            $datosAnteriores = $this->empresas->find($datos["idEmpresa"]);

            if ($this->empresas->update($datos["idEmpresa"], $datos) == false) {

                $errores = $this->empresas->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }

                return;
            } else {


                if ($certificado <> null) {
                    
                    unlink(WRITEPATH . "uploads\certificates\\" . $datosAnteriores["rfc"] . "_certificado.cer");
                    $certificado->move(WRITEPATH . "uploads\certificates", $datos["rfc"] . "_certificado.cer");
                }

                if ($archivoKey <> null) {
                    unlink(WRITEPATH . "uploads\certificates\\" . $datosAnteriores["rfc"] . "_certificado.key");
                    $archivoKey->move(WRITEPATH . "uploads\certificates", $datos["rfc"] . "_certificado.key");
                }

                if ($logo <> null) {
                    unlink("images\logo\\" . $datosAnteriores["rfc"] . "_logo.png");
                    $logo->move("images\logo", $datos["rfc"] . "_logo.png");
                }

                $dateLog["description"] = lang("empresas.logUpdated") . json_encode($datosAnteriores);
                $dateLog["user"] = $userName;

                $this->log->save($dateLog);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;
    }

    /**
     * Delete Empresas
     * @param type $id
     * @return type
     */
    public function delete($id) {

        $infoEmpresa = $this->empresas->find($id);
        helper('auth');
        $userName = user()->username;

        if (!$found = $this->empresas->delete($id)) {
            return $this->failNotFound(lang('empresas.msg.msg_get_fail'));
        }

        $logData["description"] = lang("empresas.logDeleted") . json_encode($infoEmpresa);
        $logData["user"] = $userName;

        if(file_exists(WRITEPATH . "uploads\certificates\\" . $infoEmpresa["rfc"] . "_certificado.cer")){

            unlink(WRITEPATH . "uploads\certificates\\" . $infoEmpresa["rfc"] . "_certificado.cer");

        }

        if(file_exists(WRITEPATH . "uploads\certificates\\" . $infoEmpresa["rfc"] . "_certificado.key")){
            
            unlink(WRITEPATH . "uploads\certificates\\" . $infoEmpresa["rfc"] . "_certificado.key");

        }
        
        if(file_exists("images\logo\\" . $infoEmpresa["rfc"] . "_logo.png")){

            unlink("images\logo\\" . $infoEmpresa["rfc"] . "_logo.png");

        }

        $this->empresas->purgeDeleted();

        $this->log->save($logData);
        return $this->respondDeleted($found, lang('empresas.msg_delete'));
    }

}

Para crear el modelo creamos el archivo app/models/EmpresasModel.php con el siguiente contenido

<?php

namespace App\Models;

use CodeIgniter\Model;

/**
 * @method User|null first()
 */
class EmpresasModel extends Model {

    protected $table = 'empresas';
    protected $primaryKey = 'id';
    protected $useSoftDeletes = true;
    protected $allowedFields = [
        'id', 'nombre', 'direccion', 'rfc', 'telefono', 'correoElectronico', 'diasEntrega',
        'caja', 'logo', 'certificado', 'archivoKey', 'contraCertificado', 'regimenFiscal', 'razonSocial', 'CURP',
        'codigoPostal', 'created_at ', 'updated_at ', 'deleted_at'
    ];
    protected $useTimestamps = true;
    protected $validationRules = [
        'correoElectronico' => 'required|valid_email',
        'razonSocial ' => 'required|alpha_numeric_punct|min_length[3]|is_unique[empresas.razonSocial]',
        'rfc ' => 'is_unique[empresas.rfc]',
    ];
    protected $validationMessages = [];
    protected $skipValidation = false;

}

Ahora los archivos de la vista, creamos el archivo principal de la vista en app/views/empresas.php con el siguiente código

<?= $this->include('julio101290\boilerplate\Views\load\select2') ?>
<?= $this->include('julio101290\boilerplate\Views\load\datatables') ?>
<?= $this->include('julio101290\boilerplate\Views\load\nestable') ?>
<!-- Extend from layout index -->
<?= $this->extend('julio101290\boilerplate\Views\layout\index') ?>

<!-- Section content -->
<?= $this->section('content') ?>

<?= $this->include('modulosEmpresas/modalCapturaEmpresas') ?>

<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

                <button class="btn btn-primary btnAddEmpresa" data-toggle="modal" data-target="#modalAddEmpresa"><i class="fa fa-plus"></i>

                    <?= lang('empresas.add') ?>

                </button>

            </div>
        </div>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-md-12">
                <div class="table-responsive">
                    <table id="tableEmpresas" class="table table-striped table-hover va-middle tableEmpresas">
                        <thead>
                            <tr>



                                <th>#</th>
                                <th><?= lang('empresas.fields.nombre') ?></th>
                                <th><?= lang('empresas.fields.direccion') ?></th>
                                <th><?= lang('empresas.fields.rfc') ?></th>
                                <th><?= lang('empresas.fields.logo') ?></th>
                                <th><?= lang('empresas.fields.regimenFiscal') ?></th>
                                <th><?= lang('empresas.fields.razonSocial') ?></th>
                                <th><?= lang('empresas.fields.codigoPostal') ?></th>
                                <th><?= lang('empresas.fields.CURP') ?></th>
                                <th><?= lang('empresas.fields.Created_at') ?></th>
                                <th><?= lang('empresas.fields.Update_At') ?></th>
                                <th><?= lang('empresas.fields.acciones') ?> </th>


                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.card -->

<?= $this->endSection() ?>




<?= $this->section('js') ?>
<script>

    /**
     * Cargamos la tabla
     */

    var tableEmpresas = $('#tableEmpresas').DataTable({
        processing: true,
        serverSide: true,
        responsive: true,
        autoWidth: false,
        order: [[1, 'asc']],

        ajax: {
            url: '<?= base_url(route_to('admin/empresas')) ?>',
            method: 'GET',
            dataType: "json"
        },
        columnDefs: [{
                orderable: false,
                targets: [12],
                searchable: false,
                targets: [12]

            }],
        columns: [{
                'data': 'id'
            },
            {
                'data': 'nombre'
            },

            {
                'data': 'direccion'
            },

            {
                'data': 'rfc'
            },

            {
                'data': 'logo'
            },

            {
                'data': 'regimenFiscal'
            },

            {
                'data': 'razonSocial'
            },

            {
                'data': 'codigoPostal'
            },

            {
                'data': 'CURP'
            },

            {
                'data': 'created_at'
            },

            {
                'data': 'updated_at'
            },

            {
                'data': 'CURP'
            },

            {
                "data": function (data) {
                    return `<td class="text-right py-0 align-middle">
                            <div class="btn-group btn-group-sm">
                                <button class="btn btn-warning btnEditEmpresa" data-toggle="modal" idEmpresa="${data.id}" data-target="#modalAddEmpresa">  <i class=" fa fa-edit"></i></button>
                                <button class="btn btn-danger btn-delete" data-id="${data.id}"><i class="fas fa-trash"></i></button>
                            </div>
                            </td>`
                }
            }
        ]
    });



    $(document).on('click', '#btnSaveEmpresa', function (e) {



        var idEmpresa = $("#idEmpresa").val();
        var nombre = $("#nombre").val();
        var direccion = $("#direccion").val();
        var telefono = $("#telefono").val();
        var correoElectronico = $("#correoElectronico").val();
        var razonSocial = $("#razonSocial").val();

        var rfc = $("#rfc").val();
        var CURP = $("#CURP").val();
        var regimenFiscal = $("#regimenFiscal").val();
        var codigoPostal = $("#codigoPostal").val();

        var certificado = $("#certificado").prop("files")[0];
        var archivoKey = $("#archivoKey").prop("files")[0];
        var contraCertificado = $("#contraCertificado").val();
        var logo = $("#logo").prop("files")[0];



        $("#btnSaveEmpresa").attr("disabled", true);

        var datos = new FormData();
        datos.append("idEmpresa", idEmpresa);
        datos.append("nombre", nombre);
        datos.append("direccion", direccion);
        datos.append("telefono", telefono);
        datos.append("correoElectronico", correoElectronico);
        datos.append("razonSocial", razonSocial);
        datos.append("codigoPostal", codigoPostal);

        datos.append("rfc", rfc);
        datos.append("CURP", CURP);
        datos.append("regimenFiscal", regimenFiscal);
        datos.append("certificado", certificado);
        datos.append("archivoKey", archivoKey);

        datos.append("contraCertificado", contraCertificado);
        datos.append("logo", logo);

        $.ajax({

            url: "<?= route_to('admin/empresas/save') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            //dataType:"json",
            success: function (respuesta) {


                if (respuesta.match(/Correctamente.*/)) {


                    Toast.fire({
                        icon: 'success',
                        title: "<?= lang('empresas..msg.msg_save') ?>"
                    });


                    tableEmpresas.ajax.reload();
                    $("#btnSaveEmpresa").removeAttr("disabled");


                    $('#modalAddEmpresa').modal('hide');
                } else {

                    Toast.fire({
                        icon: 'error',
                        title: respuesta
                    });

                    $("#btnSaveEmpresa").removeAttr("disabled");
                    //  $('#modalAgregarPaciente').modal('hide');

                }

            }

        }

        )




    });



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tableEmpresas").on("click", ".btnEditEmpresa", function () {

        var idEmpresa = $(this).attr("idEmpresa");



        var datos = new FormData();
        datos.append("idEmpresa", idEmpresa);

        $.ajax({

            url: "<?= base_url('admin/empresas/obtenerEmpresa') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idEmpresa").val(respuesta["id"]);
                $("#nombre").val(respuesta["nombre"]);
                $("#direccion").val(respuesta["direccion"]);
                $("#rfc").val(respuesta["rfc"]);
                $("#telefono").val(respuesta["telefono"]);
                $("#diasEntrega").val(respuesta["diasEntrega"]);
                $("#caja").val(respuesta["caja"]);
                $("#contraCertificado").val(respuesta["contraCertificado"]);
                $("#regimenFiscal").val(respuesta["regimenFiscal"]);
                $("#regimenFiscal").trigger("change");
                $("#razonSocial ").val(respuesta["razonSocial"]);
                $("#codigoPostal").val(respuesta["codigoPostal"]);
                $("#CURP").val(respuesta["CURP"]);
                $("#correoElectronico").val(respuesta["correoElectronico"]);

                if (respuesta["logo"] != "") {


                    $(".previsualizarLogo").attr('src', '<?= base_URL("images/logo") ?>' + '/' + respuesta["logo"]);

                }


            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tableEmpresas").on("click", ".btn-delete", function () {

        var idEmpresa = $(this).attr("data-id");


        console.log("eliminar");

        Swal.fire({
            title: '<?= lang('boilerplate.global.sweet.title') ?>',
            text: "<?= lang('boilerplate.global.sweet.text') ?>",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: '<?= lang('boilerplate.global.sweet.confirm_delete') ?>'
        })
                .then((result) => {
                    if (result.value) {
                        $.ajax({
                            url: `<?= base_url('admin/empresas') ?>/` + idEmpresa,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });


                            tableEmpresas.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    });


    $('#regimenFiscal').select2();


    /*=============================================
     SUBIENDO LA FOTO DEL USUARIO
     =============================================*/
    $(".logo").change(function () {

        var imagen = this.files[0];

        /*=============================================
         VALIDAMOS EL FORMATO DE LA IMAGEN SEA JPG O PNG
         =============================================*/

        if (imagen["type"] != "image/png") {

            $(".logo").val("");

            Toast.fire({
                icon: 'error',
                title: "<?= lang('empresas.imagenesFormato') ?>",
            });


        } else if (imagen["size"] > 2000000) {

            $(".logo").val("");

            Toast.fire({
                icon: 'error',
                title: "<?= lang('empresas.imagenesPeso') ?>",
            });


        } else {

            var datosImagen = new FileReader;
            datosImagen.readAsDataURL(imagen);

            $(datosImagen).on("load", function (event) {

                var rutaImagen = event.target.result;

                $(".previsualizarLogo").attr("src", rutaImagen);

            })

        }
    })

</script>
<?= $this->endSection() ?>

Ahora los archivos de la vista creamos el archivo app/views/modulosEmpresas/facturacionEmpresa.php y dentro de el metemos el siguiente código

             
<p>
<h3>Datos Facturación</h3>

<div class="form-group row">
    <label for="inputName" class="col-sm-2 col-form-label"><?= lang('empresas.fields.razonSocial') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="text" name="razonSocial" id="razonSocial" class="form-control <?= session('error.razonSocial') ? 'is-invalid' : '' ?>" value="<?= old('razonSocial') ?>" placeholder="<?= lang('empresas.fields.razonSocial') ?>" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="codigoPostal" class="col-sm-2 col-form-label"><?= lang('empresas.fields.codigoPostal') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-location-arrow"></i></span>
            </div>
            <input type="text" name="codigoPostal"  id="codigoPostal"class="form-control <?= session('error.codigoPostal') ? 'is-invalid' : '' ?>" value="<?= old('codigoPostal') ?>" placeholder="<?= lang('empresas.fields.codigoPostal') ?>" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="rfc" class="col-sm-2 col-form-label"><?= lang('empresas.fields.rfc') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-credit-card"></i></span>
            </div>
            <input type="text" name="rfc"  id="rfc" class="form-control <?= session('error.rfc') ? 'is-invalid' : '' ?>" value="<?= old('rfc') ?>" placeholder="<?= lang('empresas.fields.rfc') ?>" autocomplete="off">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="CURP" class="col-sm-2 col-form-label"><?= lang('empresas.fields.CURP') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="far fa-id-card"></i></span>
            </div>
            <input type="text" name="CURP"  id="CURP" class="form-control <?= session('error.CURP') ? 'is-invalid' : '' ?>" value="<?= old('CURP') ?>" placeholder="<?= lang('empresas.fields.CURP') ?>" autocomplete="off">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="inputSkills" class="col-sm-2 col-form-label"><?= lang('empresas.fields.regimenFiscal') ?></label>
    <div class="col-sm-8">
        <select class="form-control select" name="regimenFiscal" id="regimenFiscal" style="width: 100%;">
            
            <option value="0"><?= lang('empresas.fields.regimenFiscalOpcion') ?></option>
            <?php
            foreach ($regimenesFiscales as $key => $value) {

                echo '<option value="' . $value->id() . '">' . $value->id() . ' - ' . $value->texto() . '</option>';
            }
            ?>
        </select>
    </div>
</div>

<div class="form-group row">
    <label for="certificado" class="col-sm-2 col-form-label"><?= lang('empresas.fields.certificado') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-award"></i></span>
            </div>
            <input type="file" name="certificado" id="certificado" class="form-control <?= session('error.certificado') ? 'is-invalid' : '' ?>" value="<?= old('certificado') ?>" placeholder="<?= lang('empresas.fields.correoElectronico') ?>" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="archivoKey" class="col-sm-2 col-form-label"><?= lang('empresas.fields.archivoKey') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-key"></i></span>
            </div>
            <input type="file" name="archivoKey" id="archivoKey" class="form-control <?= session('error.certificado') ? 'is-invalid' : '' ?>" value="<?= old('archivoKey') ?>" placeholder="<?= lang('empresas.fields.archivoKey') ?>" autocomplete="off">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="contraCertificado" class="col-sm-2 col-form-label"><?= lang('empresas.fields.contraCertificado') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-unlock"></i></span>
            </div>
            <input type="text" name="contraCertificado"  id="contraCertificado" class="form-control <?= session('error.contraCertificado') ? 'is-invalid' : '' ?>" value="<?= old('contraCertificado') ?>" placeholder="<?= lang('empresas.fields.contraCertificado') ?>" autocomplete="off">
        </div>
    </div>
</div>

</p>

Ahora los archivos de la vista creamos el archivo app/views/modulosEmpresas/generalesEmpresa.php y dentro de el metemos el siguiente código

             
<p>
<h3>Datos Generales</h3>

<div class="form-group row">
    <label for="inputName" class="col-sm-2 col-form-label"><?= lang('empresas.fields.nombre') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="hidden" name="idEmpresa" id="idEmpresa" value="0">
            <input type="text" name="nombre" id="nombre" class="form-control <?= session('error.nombre') ? 'is-invalid' : '' ?>" value="<?= old('nombre') ?>" placeholder="<?= lang('empresas.fields.nombre') ?>" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="inputName" class="col-sm-2 col-form-label"><?= lang('empresas.fields.direccion') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-location-arrow"></i></span>
            </div>
            <input type="text" name="direccion"  id="direccion"class="form-control <?= session('error.direccion') ? 'is-invalid' : '' ?>" value="<?= old('direccion') ?>" placeholder="<?= lang('empresas.fields.direccion') ?>" autocomplete="off">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="telefono" class="col-sm-2 col-form-label"><?= lang('empresas.fields.telefono') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-mobile"></i></span>
            </div>
            <input type="text" name="telefono" id="telefono" class="form-control <?= session('error.telefono') ? 'is-invalid' : '' ?>" value="<?= old('telefono') ?>" placeholder="<?= lang('empresas.fields.telefono') ?>" autocomplete="off">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="correoElectronico" class="col-sm-2 col-form-label"><?= lang('empresas.fields.correoElectronico') ?></label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="far fa-envelope"></i></span>
            </div>
            <input type="text" name="correoElectronico" id="correoElectronico" class="form-control <?= session('error.correoElectronico') ? 'is-invalid' : '' ?>" value="<?= old('correoElectronico') ?>" placeholder="<?= lang('empresas.fields.correoElectronico') ?>" autocomplete="off">
        </div>
    </div>
</div>

</p>

Ahora los archivos de la vista creamos el archivo app/views/modulosEmpresas/logosEmpresa.php y dentro de el metemos el siguiente código

             
<p>
<h3>Imagenes</h3>

<div class="form-group">

    <div class="panel"><?= lang("empresas.fields.logo") ?></div>

    <input type="file" class="logo" name="logo" id="logo">

    <p class="help-block"><?= lang("empresas.imagenesPesoMaximo") ?></p>

    <img src="<?= base_url("images/logo/anonymous.png") ?>" class="img-thumbnail previsualizarLogo" width="100px">

    <input type="hidden" name="logoActual" id="logoActual">

</div>

</p>

Ahora los archivos de la vista creamos el archivo app/views/modulosEmpresas/modalCapturaEmpresas.php y dentro de el metemos el siguiente código

<!-- Modal Empresas -->
<div class="modal fade" id="modalAddEmpresa" tabindex="-1" role="dialog" aria-labelledby="modalAddEmpresa" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"><?= lang('empresas.createEdit') ?></h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">

                <ul class="nav nav-tabs" id="myTab" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#generales" type="button" role="tab" aria-controls="home" aria-selected="true">Generales</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="profile-tab" data-toggle="tab" data-target="#datosFacturacion" type="button" role="tab" aria-controls="profile" aria-selected="false">Facturacion</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="contact-tab" data-toggle="tab" data-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false">Logos / Imagenes</button>
                    </li>
                </ul>
                <form id="form-empresa" class="form-horizontal">  
                    <div class="tab-content" id="myTabContent">



                        <div class="tab-pane fade show active" id="generales" role="tabpanel" aria-labelledby="generales">

                            <?= $this->include('modulosEmpresas/generalesEmpresa') ?>

                        </div>
                        <div class="tab-pane fade" id="datosFacturacion" role="tabpanel" aria-labelledby="datosFacturacion">

                            <?= $this->include('modulosEmpresas/facturacionEmpresa') ?>

                        </div>
                        <div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
                            
                            <?= $this->include('modulosEmpresas/logosEmpresa') ?>
                        
                        </div>


                    </div>

                </form>


            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal"><?= lang('boilerplate.global.close') ?></button>
                <button type="button" class="btn btn-primary btn-sm" id="btnSaveEmpresa"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

<?= $this->section('js') ?>


<script>

    $(document).on('click', '.btnAddEmpresa', function (e) {
        
        console.log("asd");
    
        $(".form-control").val("");

        $("#idEmpresa").val("0");

         $(".previsualizarLogo").attr('src','<?= base_URL("images/logo") ?>/anonymous.png'  );
        
        $("#btnSaveEmpresa").removeAttr("disabled");

    });

    /* 
     * AL hacer click al editar
     */



    $(document).on('click', '.btnEditarEmpresa', function (e) {


        var idEmpresa = $(this).attr("idEmpresa");

        //LIMPIAMOS CONTROLES
        $(".form-control").val("");

        $("#idEmpresa").val(idEmpresa);
        $("#btnGuardarEmpresa").removeAttr("disabled");

    });




</script>


<?= $this->endSection() ?>

En el archivo app/config/routes.php quedaria de la siguiente forma

$routes->group('admin', function ($routes) {

    $routes->resource('empresas', [
        'filter' => 'permission:empresas-permisos',
        'controller' => 'EmpresasController',
        'except' => 'show'
    ]);

    $routes->post('empresas/save', 'EmpresasController::save');
    $routes->post('empresas/obtenerEmpresa', 'EmpresasController::obtenerEmpresa');
});

En los archivos de lenguaje para ingles creamos el archivo app/Language/en/empresas.php con el siguiente contenido

<?php

$empresas["logDescription"] = "The companie was saved with the following data:";
$empresas["logUpdate"] = "The companie was updated  with the following data:";
$empresas["logDeleted"] = "The companie was deleted  with the following data:";
$empresas["msg_delete"] = "The companie was deleted  correctly:";

$empresas["add"] = "Add companie";
$empresas["edit"] = "Edit companie";
$empresas["createEdit"] = "Create / Edith";
$empresas["title"] = "companie management";
$empresas["subtitle"] = "companie list";
$empresas["fields"]["nombre"] = "Name";
$empresas["fields"]["direccion"] = "direction";
$empresas["fields"]["rfc"] = "RFC";
$empresas["fields"]["logo"] = "logo";
$empresas["fields"]["certificado"] = "certificate";
$empresas["fields"]["archivoKey"] = "KeyFile";
$empresas["fields"]["contraCertificado"] = "Password Certificate";
$empresas["fields"]["regimenFiscal"] = "Tax Regime";
$empresas["fields"]["regimenFiscalOpcion"] = "Select tax regime";
$empresas["fields"]["razonSocial "] = "Business name";
$empresas["fields"]["codigoPostal "] = "Postal Code ";
$empresas["fields"]["CURP"] = "CURP";



$empresas["fields"]["Created_at"] = "Created At";
$empresas["fields"]["Update_At"] = "Update At";

$empresas["msg"]["msg_save"] = "The companie has been correctly saved.";
$empresas["msg"]["msg_insert"] = "The companie has been correctly added.";
$empresas["msg"]["msg_update"] = "The companie has been correctly modified.";
$empresas["msg"]["msg_delete"] = "The companie has been correctly deleted.";
$empresas["msg"]["msg_get"] = "The companie has been successfully get.";
$empresas["msg"]["msg_get_fail"] = "The vehicle not found or already deleted.";

$empresas["imagenesFormato"] = "The image must be in PNG format!";
$empresas["imagenesPeso"] = "The image must not be larger than 2MB!";
$empresas["imagenesPesoMaximo"] = "Maximum weight of the photo 1MB";

$empresas["certExtensionIncorrect"] = "Extensión certificate incorrect";
$empresas["keyFileExtensionIncorrect"] = "Extensión key file incorrect";
$empresas["pngFileExtensionIncorrect"] = "This image not is PNG";


return $empresas;

En los archivos de lenguaje para ingles creamos el archivo app/Language/es/empresas.php con el siguiente contenido

<?php
$empresas["logDescription"] = "El Empresa ha sido guardado con los siguientes datos:";
$empresas["logUpdate"] = "EL Empresa ha sido actualizado con los siguientes datos";
$empresas["logDeleted"] = "El Empresa ha sido eliminado con los siguientes datos:";

$empresas["add"] = "Agregar Empresa";
$empresas["edit"] = "Editar Empresa";
$empresas["createEdit"] = "Crear / Editar";
$empresas["title"] = "Administración de Empresas";
$empresas["subtitle"] = "Lista de Empresas";
$empresas["fields"]["nombre"] = "Nombre";
$empresas["fields"]["direccion"] = "Direccion";
$empresas["fields"]["rfc"] = "RFC";
$empresas["fields"]["logo"] = "Logo";
$empresas["fields"]["certificado"] = "Certificado";
$empresas["fields"]["archivoKey"] = "Archivo Key";
$empresas["fields"]["telefono"] = "Telefono";
$empresas["fields"]["contraCertificado"] = "Contra Certificado";
$empresas["fields"]["regimenFiscal"] = "Regimen Fiscal";
$empresas["fields"]["regimenFiscalOpcion"] = "Seleccionar regimen fiscal";
$empresas["fields"]["razonSocial"] = "Razon Social";
$empresas["fields"]["codigoPostal"] = "Código Postal";
$empresas["fields"]["CURP"] = "CURP";
$empresas["fields"]["correoElectronico"] = "Correo Electronico";
$empresas["fields"]["acciones"] = "Acciones";

$empresas["msg"]["msg_save"] = "La Empresa ha sido correctamente guardada.";
$empresas["msg"]["msg_insert"] = "La Empresa ha sido correctamente agregado.";
$empresas["msg"]["msg_update"] = "La Empresa ha sido correctamente actualizado.";
$empresas["msg"]["msg_delete"] = "La Empresa ha sido correctamente eliminado.";
$empresas["msg"]["msg_get"] = "La Empresa ha sido correctamente obtenido.";
$empresas["msg"]["msg_get_fail"] = "No se encontro Empresa o fue eliminado.";

// Validations
$empresas["imagenesFormato"] = "¡La imagen debe estar en formato  PNG!";
$empresas["imagenesPeso"] = "¡La imagen no debe pesar más de 2MB!";
$empresas["imagenesPesoMaximo"] = "Peso máximo de la foto 1MB";

$empresas["certExtensionIncorrect"] = "Extensión del certificado incorrecto";
$empresas["keyFileExtensionIncorrect"] = "Extensión del archivo key incorrecto";
$empresas["pngFileExtensionIncorrect"] = "Solo se permiten imagenes con formato png";



return $empresas;

Creamos el siguiente directorio para las imagenes, public/images/logo

Creamos el archivo app/databe/migrations/2023-02-14001801_Empresas.php con el siguiente contenido, este nos servirá para crear la tabla

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Empresas extends Migration {

    public function up() {
        // Empresas
        $this->forge->addField([
            'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
            'nombre' => ['type' => 'varchar', 'constraint' => 500, 'null' => false],
            'direccion' => ['type' => 'varchar', 'constraint' => 30, 'null' => true],
            'rfc' => ['type' => 'varchar', 'constraint' => 14, 'null' => false],
            'telefono' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
            'correoElectronico' => ['type' => 'varchar', 'constraint' => 256, 'null' => true],
            'diasEntrega' => ['type' => 'varchar', 'constraint' => 8, 'null' => true],
            'caja' => ['type' => 'varchar', 'constraint' => 255, 'null' => true],
            'logo' => ['type' => 'varchar', 'constraint' => 1024, 'null' => true],
            'certificado' => ['type' => 'varchar', 'constraint' => 1024, 'null' => true],
            'archivoKey' => ['type' => 'varchar', 'constraint' => 68, 'null' => true],
            'contraCertificado' => ['type' => 'varchar', 'constraint' => 68, 'null' => true],
            'regimenFiscal' => ['type' => 'varchar', 'constraint' => 68, 'null' => true],
            'razonSocial' => ['type' => 'varchar', 'constraint' => 68, 'null' => true],
            'codigoPostal' => ['type' => 'varchar', 'constraint' => 68, 'null' => true],
            'CURP' => ['type' => 'varchar', 'constraint' => 68, 'null' => true],
            'created_at' => ['type' => 'datetime', 'null' => true],
            'updated_at' => ['type' => 'datetime', 'null' => true],
            'deleted_at' => ['type' => 'datetime', 'null' => true],
        ]);

        $this->forge->addKey('id', true);

        $this->forge->createTable('empresas', true);
    }

    public function down() {
        $this->forge->dropTable('empresas', true);
    }

}

Creamos el modelo para la bitacora app/models/LogModel.php con el siguiente contenido

<?php

namespace App\Models;

use CodeIgniter\Model;

class LogModel extends Model
{
    protected $table      = 'log';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['id','description', 'user',"created_at","updated_at"];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    = [];
    protected $validationMessages = [];
    protected $skipValidation     = false;

  
}

Creamos el archivo de migración para LogModel en app/database/migration/2023-02-07-165412_Log.php con el siguiente contenido

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Log extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 5,
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'description' => [
                'type' => 'VARCHAR',
                'constraint' => '256',
            ],
            'user' => [
                'type' => 'TEXT',
                'null' => true,
            ],
 
            'created_at' => ['type' => 'datetime', 'null' => true],
            'updated_at' => ['type' => 'datetime', 'null' => true],
            'deleted_at' => ['type' => 'datetime', 'null' => true],
        ]);

        $this->forge->addKey('id', true);
        $this->forge->createTable('log', true);
    }

    public function down()
    {
        $this->forge->dropTable('log');
    }
}

Por ultimo corremos el siguiente código en la terminal para correr la migración y así nos genere la tabla empresas y bitacora en la base de datos

php spark migrate
Y listo ya tenemos nuestro catalogo de empresas
Video Demostrativo

CodeIgniter 4 Aplicacion Boilerplate Plantilla

Que es boilerplate? normalmente en el ámbito de desarrollo de aplicaciones se le llama boilerplate a todo ese código repetitivo que usamos en un proyecto, o mas bien lo que siempre usamos en todos los proyectos, como login, menús, permisos.

A continuación les dejo un boilerplate para CodeIgniter el cual hice una bifurcación para adaptarlo a nuestras necesidades.

Este paquete para CodeIgniter 4 sirve como plataforma básica para crear rápidamente una aplicación administrativa. Incluye creación y gestión de perfiles, gestión de usuarios, roles, permisos y un menú generado dinámicamente.

Características

  • Tema backend configurable AdminLTE 3
  • CSS framework Bootstrap 4
  • Iconos de Font Awesome 5
  • Permisos basados en roles (RBAC) proporcionados porMyth/Auth
  • Generación de menús dinamicos
  • Traducciones en English / Indonesian / Spanish

Este proyecto aún está en sus primeras etapas de desarrollo… ¡no dude en contribuir!

El autor original es agungsugiarto/boilerplate https://github.com/agungsugiarto/boilerplate, solo hicimos un fork para adaptarlo a nuevas necesidades como traducirlo al español y hacerlo funcional en xampp/lampp

El fuente con los cambios mas recientes de fork estan en github https://github.com/julio101290/boilerplate

Instalación

  1. Obtener el modulo mediante composer
composer require julio101290/boilerplate

2. Establezca CI_ENVIRONMENT, baseURL, página de índice y configuración de la base de datos en su archivo .env según su base de datos existente (si no tiene un archivo .env, puede copiar primero desde el archivo env: cp env .env primero). Si la base de datos no existe, primero cree la base de datos.

# .env file
CI_ENVIRONMENT = development

app.baseURL = 'http://localhost:8080'
app.indexPage = ''

database.default.hostname = localhost
database.default.database = boilerplate
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi

3. Correr el comando para publicar de auth

php spark auth:publish

Publish Migration? [y, n]: y
  created: Database/Migrations/2017-11-20-223112_create_auth_tables.php
  Remember to run `spark migrate -all` to migrate the database.
Publish Models? [y, n]: n
Publish Entities? [y, n]: n
Publish Controller? [y, n]: n
Publish Views? [y, n]: n
Publish Filters? [y, n]: n
Publish Config file? [y, n]: y
  created: Config/Auth.php
Publish Language file? [y, n]: n

NOTA: Todo lo relacionado con cómo configurar la autenticación puede encontrar en Myth/Auth.

¿Ya está listo? ¡¡No tan rapido!! 😉 Después de publicar Config/Auth.php, debe cambiar las $views públicas con estas líneas a continuación:

public $views = [
    'login'           => 'julio101290\boilerplate\Views\Authentication\login',
    'register'        => 'julio101290\boilerplate\Views\Authentication\register',
    'forgot'          => 'julio101290\boilerplate\Views\Authentication\forgot',
    'reset'           => 'julio101290\boilerplate\Views\Authentication\reset',
    'emailForgot'     => 'julio101290\boilerplate\Views\Authentication\emails\forgot',
    'emailActivation' => 'julio101290\boilerplate\Views\Authentication\emails\activation',
];

Abra app\Config\Filters.php, busque $aliases y agregue estas líneas a continuación:

public $aliases = [
    'login'      => \Myth\Auth\Filters\LoginFilter::class,
    'role'       => \julio101290\boilerplate\Filters\RoleFilter::class,
    'permission' => \julio101290\boilerplate\Filters\PermissionFilter::class,
];

4. Ejecute publish, migrate y seed del boilerplate

php spark boilerplate:install

Abra app\Config\validation.php, busque $ruleSets y agregue estas líneas a continuación:

public $$ruleSets = [
    \Myth\Auth\Authentication\Passwords\ValidationRules::class,
];

Abra app\entities\Users.php, busque $casts y agregue estas líneas a continuación:


   protected $casts = [
        'username' => 'string',
        'email' => 'string',
        'firstname' => 'string',
        'lastname' => 'string',
        'active' => 'boolean',
        'force_pass_reset' => 'boolean',
    ];

Ejecute el servidor de desarrollo:

php spark serve

Abrir en el navegador http://localhost:8080/admin

Default user and password
+----+--------+-------------+
| No | User   | Password    |
+----+--------+-------------+
| 1  | admin  | super-admin |
| 2  | user   | super-user  |
+----+--------+-------------+

Configuraciones

Plantilla de configuración

Puede configurar el controlador de tablero predeterminado y el tema de back-end en app\Config\Boilerplate.php

class Boilerplate extends BaseConfig
{
    public $appName = 'Boilerplate';

    public $dashboard = [
        'namespace'  => 'julio101290\boilerplate\Controllers',
        'controller' => 'DashboardController::index',
        'filter'     => 'permission:back-office',
    ];
// App/Config/Boilerplate.php

Uso

Puede encontrar cómo funciona con las rutas de código de lectura, el controlador y las vistas, etc. Finalmente… ¡Feliz codificación!

Contribuciones

Las contribuciones son muy bienvenidas.

Licencia

Este paquete es software gratuito distribuido bajo los términos de la licencia MIT.

CI 4.0 MedicalSoft Modulo Para Cita y Creando nuestros Propios Helpers personalizados

En este apartado vamos a crear nuestro modulo de citas medicas que posteriormente podremos convertir en consulta, a su vez veremos como crear nuestros propios helpers y usarlos

Que es un helper en codeigniter, pues no es mas que funciones o utilerías que nos pueden servir en diferentes partes del sistema

Creamos la tabla para citas

-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 08-01-2023 a las 23:44:15
-- Versión del servidor: 10.4.17-MariaDB
-- Versión de PHP: 7.4.15

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Base de datos: `medicalsoft`
--

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `citas`
--

CREATE TABLE `citas` (
  `id` int(11) NOT NULL,
  `idPaciente` int(11) DEFAULT NULL,
  `fechaHora` datetime NOT NULL,
  `hastaFechaHora` datetime DEFAULT NULL,
  `observaciones` varchar(1024) COLLATE utf8_spanish2_ci NOT NULL,
  `created_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `estado` varchar(15) COLLATE utf8_spanish2_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;

--
-- Índices para tablas volcadas
--

--
-- Indices de la tabla `citas`
--
ALTER TABLE `citas`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `citas`
--
ALTER TABLE `citas`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Y como de costumbre creamos primeramente el archiv CitasModel.php en la carpeta app/models y le ponemos el siguiente código

<?php

namespace App\Models;

use CodeIgniter\Model;

class CitasModel extends Model {

    protected $table = 'citas';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id', 'idPaciente', 'fechaHora', 'hastaFechaHora', 'observaciones', 'created_at', 'updated_at'];
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [
        'observaciones' => 'required|alpha_numeric_space|min_length[3]'
    ];
    protected $validationMessages = [];
    protected $skipValidation = false;

    public function mdlObtenerDatos() {

        $resultado = $this->db->table('citas a, pacientes b')
                ->select('a.id,concat(b.nombres,\' \',b.apellidos) as nombrePaciente,a.observaciones,a.fechaHora,a.hastaFechaHora,estado,a.created_at,a.updated_at')
                ->where('a.idPaciente', 'b.id', false)
                ->where('a.deleted_at', null);

        return $resultado;
    }

    public function mdlObtenerCita($idCita) {

        $resultado = $this->db->table('citas a, pacientes b')
                ->select('a.id,concat(b.nombres,\' \',b.apellidos) as nombrePaciente,a.observaciones,a.fechaHora,a.hastaFechaHora,estado,a.created_at,a.updated_at,a.idPaciente')
                ->where('a.idPaciente', 'b.id', false)
                ->where('a.deleted_at', null)
                ->where('a.id', $idCita,null)->get()->getResultArray();

        return $resultado[0];
    }

}

Posteriormente creamos el archivo CitasController.php en la carpeta app/controller y le insertamos el siguiente código

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\CitasModel;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class CitasController extends BaseController {

    use ResponseTrait;

    protected $bitacora;
    protected $citas;

    public function __construct() {
        $this->citas = new CitasModel();
        $this->bitacora = new BitacoraModel();
        helper('menu');
      
    }

    public function index() {


        if ($this->request->isAJAX()) {

            /*
              $start = $this->request->getGet('start');
              $length = $this->request->getGet('length');
              $search = $this->request->getGet('search[value]');
              $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
              $dir = $this->request->getGet('order[0][dir]');
             */

            $datos = $this->citas->mdlObtenerDatos();  
                    //where('deleted_at', null);
            // $resultado = $this->bitacora->findAll();
            // $this->bitacora->getResource()->countAllResults(),
            // $this->bitacora->getResource($search)->countAllResults()
            //      var_dump($datos);


            return \Hermawan\DataTables\DataTable::of($datos)->add('action', function ($row) {
                                return " <div class=\"btn-group\">
                          
                  <button class=\"btn btn-warning btnEditarCita\" data-toggle=\"modal\" idCita=\"$row->id\" data-target=\"#modalAgregarCitas\">  <i class=\" fa fa-edit \"></i></button>
                  <button class=\"btn btn-danger btnEliminarCita\" idCita=\"$row->id\"><i class=\"fa fa-times\"></i></button></div>";
                            }, 'last')
                            ->toJson();
        }

        
        $fechaActual =  fechaMySQLADateTimeHTML5(fechaHoraActual());
        
        $titulos["fecha"] =  $fechaActual;
        $titulos["title"] = lang('citas.title');
        $titulos["subtitle"] = lang('citas.subtitle');

        return view('citas', $titulos);
    }

    /*
     * Lee paciente
     */

    public function traeCita() {


        $idCita = $this->request->getPost("idCita");
        $datosCita = $this->citas->mdlObtenerCita($idCita);

        echo json_encode($datosCita);
    }

    /*
     * Guarda o actualiza paciente
     */

    public function guardar() {


        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;

        $datos = $this->request->getPost();

        if ($datos["idCita"] == 0) {


            try {


                if ($this->citas->save($datos) === false) {

                    $errores = $this->citas->errors();

                    foreach ($errores as $field => $error) {

                        echo $error . " ";
                    }

                    return;
                }

                $datosBitacora["descripcion"] = "Se guardo la medicamento con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;

                $this->bitacora->save($datosBitacora);

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            if ($this->citas->update($datos["idCita"], $datos) == false) {
                
                $errores = $this->citas->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }
                
                return;
               
            } else {

                $datosBitacora["descripcion"] = "Se actualizo el medicamento con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;
                $this->bitacora->save($datosBitacora);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;


    }
    
    
    
     public function delete($id)
    {
        if (!$found = $this->citas->delete($id)) {
            return $this->failNotFound(lang('citas.msg.msg_get_fail'));
        }
        
        $infoCita = $this->citas->find($id);
        
        $datosBitacora["descripcion"] = "Se elimino la medicamento que contenia los siguientes datos ". json_encode($infoCita);
        
        $this->bitacora->save($datosBitacora);
        return $this->respondDeleted($found, lang('citas.msg.msg_delete'));
    }

}

Luego Creamos la vista citas.php en app/views

<?= $this->include('agungsugiarto\boilerplate\Views\load\select2') ?>
<?= $this->include('agungsugiarto\boilerplate\Views\load\datatables') ?>
<?= $this->include('agungsugiarto\boilerplate\Views\load\nestable') ?>
<!-- Extend from layout index -->
<?= $this->extend('agungsugiarto\boilerplate\Views\layout\index') ?>

<!-- Section content -->
<?= $this->section('content') ?>

<?= $this->include('citasModulos\modalCaptura') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

                <button class="btn btn-primary btnAgregarCitas" data-toggle="modal" data-target="#modalAgregarCitas"><i class="fa fa-plus"></i>

                    <?= lang('citas.add') ?>

                </button>

            </div>
        </div>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-md-12">
                <div class="table-responsive">
                    <table id="tablaCitas" class="table table-striped table-hover va-middle tablaCitas">
                        <thead>
                            <tr>



                                <th>#</th>

                                <th><?= lang('citas.nombrePaciente') ?></th>
                                <th><?= lang('citas.observaciones') ?></th>
                                <th><?= lang('citas.fechaHora') ?></th>
                                <th><?= lang('citas.hastaFechaHora') ?></th>
                                <th><?= lang('citas.estado') ?></th>


                                <th><?= lang('citas.createdAt') ?></th>
                                <th><?= lang('citas.updateAt') ?></th>
                                <th><?= lang('citas.actions') ?> </th>


                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.card -->

<?= $this->endSection() ?>




<?= $this->section('js') ?>
<script>

    /**
     * Cargamos la tabla
     */


    function cargaTabla() {



        $('.tablaCitas').DataTable({
            "ajax": "<?= route_to('admin/citas') ?>",
            "deferRender": true,
            "serverSide": true,
            "retrieve": true,
            "processing": true

        });

    }


    cargaTabla();



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tablaCitas").on("click", ".btnEditarCita", function () {

        var idCita = $(this).attr("idCita");


        console.log("idCita ", idCita);

        var datos = new FormData();
        datos.append("idCita", idCita);

        $.ajax({

            url: "<?= route_to('admin/citas/traeCita') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idCita").val(respuesta["id"]);
                $("#observaciones").val(respuesta["observaciones"]);
                $("#fechaHora").val(respuesta["fechaHora"]);
                $("#hastaFechaHora").val(respuesta["hastaFechaHora"]);


                //$("#pacientes").val(respuesta["idPaciente"]);

                $("#pacientes").empty() //empty select
                        .append($("<option/>") //add option tag in select
                                .val(respuesta["idPaciente"]) //set value for option to post it
                                .text(respuesta["nombrePaciente"])) //set a text for show in select
                        .val(respuesta["idPaciente"]) //select option of select2
                        .trigger("change");


            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tablaCitas").on("click", ".btnEliminarCita", function () {

        var idCita = $(this).attr("idCita");




        Swal.fire({
            title: '<?= lang('boilerplate.global.sweet.title') ?>',
            text: "<?= lang('boilerplate.global.sweet.text') ?>",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: '<?= lang('boilerplate.global.sweet.confirm_delete') ?>'
        })
                .then((result) => {
                    if (result.value) {
                        $.ajax({
                            url: `<?= route_to('admin/citas') ?>/` + idCita,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });

                            $(".tablaCitas").DataTable().destroy();
                            cargaTabla();
                            //tableUser.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })




</script>
<?= $this->endSection() ?>

Luego en la carpeta app/views creamos la carpeta citasModulos, creamos el archivo modalCaptura.php y metemos el siguiente código

<!-- Modal Citas -->

<div class="modal fade" id="modalAgregarCitas" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"><?= lang('citas.createEdit') ?></h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <form id="form-enfermedad" class="form-horizontal">
                    <!-- CSRF token --> 
                    <input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />

                    <input type="hidden" id="idCita" name="idCita" value="0">




                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.nombrePaciente') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>

                                <select id='pacientes'  name='pacientes' style='width: 80%;'>
                                    <option value='0'><?= lang('citas.seleccionePaciente') ?></option>
                                </select>


                            </div>
                        </div>
                    </div>


                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.observaciones') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>

                                <textarea class="form-control <?= session('error.observaciones ') ? 'is-invalid' : '' ?>" rows="3" placeholder="<?= lang('citas.observaciones') ?>" id="observaciones" name="observaciones" autocomplete="off"></textarea>

                            </div>
                        </div>
                    </div>



                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.fechaHora') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>


                                <input type="datetime-local" id="fechaHora" name="fechaHora" value="<?= $fecha ?>">


                            </div>
                        </div>
                    </div>


                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.hastaFechaHora') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>


                                <input type="datetime-local" id="hastaFechaHora" name="hastaFechaHora" value="<?= $fecha ?>">


                            </div>
                        </div>
                    </div>



                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal"><?= lang('boilerplate.global.close') ?></button>
                <button type="button" class="btn btn-primary btn-sm" id="btnGuardarCita"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

<?= $this->section('js') ?>


<script>

    $(document).on('click', '.btnAgregarCitas', function (e) {

        console.log("asdasd");
        $(".form-control").val("");

        $("#idCita").val("0");

        $("#btnGuardarCita").removeAttr("disabled");

    })

    /* 
     * AL hacer click al editar
     */



    $(document).on('click', '.btnGuardarCita', function (e) {


        var idCita = $(this).attr("idCita");

        //LIMPIAMOS CONTROLES
        $(".form-control").val("");

        $("#idCita").val(idCita);
        $("#btnGuardarCita").removeAttr("disabled");

    })

    /**
     * Guardar paciente
     */

    $(document).on('click', '#btnGuardarCita', function (e) {

        var idCita = $("#idCita").val();
        var observaciones = $("#observaciones").val();
        var idPaciente = $("#pacientes").val();
        var fechaHora = $("#fechaHora").val();
        var hastaFechaHora = $("#hastaFechaHora").val();
       

        console.log("OBSERVACIONES:",observaciones);

        $("#btnGuardarCita").attr("disabled", true);


        var datos = new FormData();
        datos.append("idCita", idCita);
        datos.append("observaciones", observaciones);
        datos.append("idPaciente", idPaciente);
        datos.append("fechaHora", fechaHora);
        datos.append("hastaFechaHora", hastaFechaHora);


        $.ajax({

            url: "<?= route_to('admin/citas/guardar') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            //dataType:"json",
            success: function (respuesta) {


                if (respuesta.match(/Correctamente.*/)) {


                    Toast.fire({
                        icon: 'success',
                        title: "Guardado Correctamente"
                    });


                    $('.tablaCitas').DataTable().destroy();
                    cargaTabla();
                    $("#btnGuardarCita").removeAttr("disabled");


                    $('#modalAgregarCitas').modal('hide');
                } else {

                    Toast.fire({
                        icon: 'error',
                        title: respuesta
                    });

                    $("#btnGuardarCita").removeAttr("disabled");
                    //  $('#modalAgregarCita').modal('hide');

                }

            }

        }

        )




    });



    // Initialize select2
    $("#pacientes").select2({
        ajax: {
            url: "<?= site_url('admin/pacientes/traerPacientesAjax') ?>",
            type: "post",
            dataType: 'json',
            delay: 250,
            data: function (params) {
                // CSRF Hash
                var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name
                var csrfHash = $('.txt_csrfname').val(); // CSRF hash

                return {
                    searchTerm: params.term, // search term
                    [csrfName]: csrfHash // CSRF Token
                };
            },
            processResults: function (response) {

                // Update CSRF Token
                $('.txt_csrfname').val(response.token);

                return {
                    results: response.data
                };
            },
            cache: true
        }
    });



</script>


<?= $this->endSection() ?>

En este modulo como se vieron el archivo CitasController.php se usaron unas funciones para el formato de las fechas dichas funciones estan en el helper, estas funciones nos sirvieron anteriormente en JCPOS2021, entonces en la carpeta app/Helpers creamos el archivo utilerias_helper.php y metemos el siguiente código

<?php

function strMenuActivo($strMenu1, $strMenu2) {
    if ($strMenu1 == $strMenu2) {
        $respuesta = 'class="active"';
    } else {
        $respuesta = "";
    }
    return $respuesta;
}

//SI LA VARIABLE ESTA VACIA O NO SETA DECLARADA MANDARA CERO SIEMPRE, ES COMO EL VAL DE VISUAL BASIC 6.0

function esCero($value) {

    if (empty($value)) {
        return "0";
    } else {
        return $value;
    }
}

//FECHA SQL PARA GUARDAR EN BASE DE DATOS

function fechaSQL($fecha) {

    return date("Ymd", strtotime($fecha));
}

// CONVIERTE FECHA MYSQLDATETIME A HTML5
function fechaMySQLADateTimeHTML5($fecha) {

    return date("Y-m-d", strtotime($fecha)) . "T" . date("H:i:s", strtotime($fecha));
}

function agregarMinutos($fecha, $minutos) {


    return date("Y/m/d h:i:s", strtotime($fecha . "+ $minutos minutes"));

    /*
      $fecha1= new DateTime($fecha);
      $fecha1->add(new DateInterval('PT10H30S'));
      return $date->format('Y-m-d H:i:s') . "\n";
     * 
     */
}

//CONVIERTE LA FECHA EN PERIODO
function fechaPeriodo($fecha) {

    return date("Ym", strtotime($fecha));
}

//OBTIENE FECHA ACTUAL
function fechaActual() {

    return date("Y/m/d");
}

//OBTIENE FECHA HORA ACTUAL

function fechaHoraActual() {

    return date("Y-m-d H:i:s ", time());
}

//DIFERENCIA ENTRE MINUTOS
function diferenciaMinutos($fecha_i, $fecha_f) {
    $minutos = (strtotime($fecha_i) - strtotime($fecha_f)) / 60;

    $minutos = abs($minutos);
    $minutos = floor($minutos);

    return $minutos;
}

function strSellar($llave, $password, $cadenaOriginal) {


    $archivoPem = "/tmp/llave.key.pem";
    $comando = "openssl pkcs8 -inform DER -in $llave -passin pass:$password -out $archivoPem";

    exec($comando);
    $sello = "ok";

    //Sellar
    $archivo = openssl_pkey_get_private(file_get_contents($archivoPem));
    $sig = "";
    openssl_sign($cadenaOriginal, $sig, $archivo, OPENSSL_ALGO_SHA256);

    $sello = base64_encode($sig);

    return $sello;
}

//SOLO DIA
function dia($fecha) {

    return date("d", strtotime($fecha));
}

//SOLO MES
function mes($fecha) {

    return date("m", strtotime($fecha));
}

//SOLO AÑO
function año($fecha) {

    return date("Y", strtotime($fecha));
}

Ahora en app/lenguajes/en/ creamos el archivo citas.php y metemos el siguiente archivo

<?php

$citas["observaciones"] = "Observations";
$citas["nombrePaciente"] = "Patient´s Name";
$citas["fechaHora"] = "Start Date";
$citas["hastaFechaHora"] = "End Date";

$citas["createdAt"] = "Date Creation";
$citas["updateAt"] = "Date Update";
$citas["add"] = "add appointment";
$citas["actions"] = "Acciones";
$citas["estado"] = "status";
$citas["createEdit"] = "add  / update appointment";
$citas["seleccionePaciente"] = "Select an Patient";
$citas["title"] = "appointment";
$citas["subtitle"] = "List of appointments";

$citas["msg_delete"] = "The quote has been removed .";
$citas["msg_get_fail"] = "The appointment does not exist or was deleted.";





return $citas;

Ahora en app/lenguajes/es/ creamos el archivo citas.php y metemos el siguiente archivo

<?php

$citas["observaciones"] = "Observaciones";
$citas["nombrePaciente"] = "Nombre Paciente";
$citas["fechaHora"] = "Fecha Inicio Cita";
$citas["obsevaciones"] = "Observaciones";
$citas["hastaFechaHora"] = "Fin Cita";

$citas["createdAt"] = "Fecha Creación";
$citas["updateAt"] = "Fecha de Modificación";
$citas["add"] = "Agregar Cita";
$citas["actions"] = "Acciones";
$citas["estado"] = "Estado";
$citas["createEdit"] = "Crear  / Editar Citas";
$citas["seleccionePaciente"] = "Seleccione un paciente";
$citas["title"] = "Citas";
$citas["subtitle"] = "Lista de Citas";

$citas["msg_delete"] = "La cita ha sido eliminada .";
$citas["msg_get_fail"] = "La cita no existe o fue eliminada.";

return $citas;

Luego ya por ultimo creamos las rutas en app/config/routes.php metemos el siguiente codigo

    $routes->resource('citas', [
        'filter' => 'permission:citas-permiso',
        'controller' => 'CitasController',
        'except' => 'show'
    ]);

    $routes->post('citas/guardar', 'CitasController::guardar');

    $routes->post('citas/traeCita', 'CitasController::traeCita');
Y listo ya tenemos nuestro modulo de citas y aprendimos como usar los Helpers que creamos

CI 4.0 MedicalSoft CRUD Para Pacientes

Ahora veremos como crear el CRUD de pacientes en el framework CodeIgniter 4.0 con los siguientes datos, Nombres, Apellidos, DNI, Correo Electrónico, para ello creamos la siguiente tabla.

Leer Mas: CI 4.0 MedicalSoft CRUD Para Pacientes
-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 04-01-2023 a las 18:20:08
-- Versión del servidor: 10.4.17-MariaDB
-- Versión de PHP: 7.4.15

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Base de datos: `medicalsoft`
--

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `pacientes`
--

CREATE TABLE `pacientes` (
  `id` int(11) NOT NULL,
  `nombres` varchar(256) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `apellidos` varchar(256) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `dni` varchar(32) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `telefono` varchar(16) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `correoElectronico` varchar(64) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;

--
-- Índices para tablas volcadas
--

--
-- Indices de la tabla `pacientes`
--
ALTER TABLE `pacientes`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `pacientes`
--
ALTER TABLE `pacientes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Creamos el archivo PacientesModel.php en la carpeta model para el acceso a la tabla de la base de datos

<?php

namespace App\Models;

use CodeIgniter\Model;

class PacientesModel extends Model
{
    protected $table      = 'pacientes';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['id','nombres', 'apellidos','dni','telefono','correoElectronico','created_at','updated_at'];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    =  [
        'nombres'     => 'required|alpha_numeric_space|min_length[3]',
        'apellidos'     => 'required|alpha_numeric_space|min_length[5]',
        'correoElectronico'        => 'required|valid_email|is_unique[users.email]'
        
    ];
    protected $validationMessages = [];
    protected $skipValidation     = false;
    
   
}

Creamos el archivo PacientesController.php alli tendremos la funciones para guardar, leer y modificar, creo que no falta decir que va en la carpeta controller

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use \App\Models\PacientesModel;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class PacientesController extends BaseController {

    use ResponseTrait;

    protected $bitacora;
    protected $pacientes;

    public function __construct() {
        $this->pacientes = new PacientesModel();
        $this->bitacora = new BitacoraModel();
        helper('menu');
    }

    public function index() {


        if ($this->request->isAJAX()) {

            /*
              $start = $this->request->getGet('start');
              $length = $this->request->getGet('length');
              $search = $this->request->getGet('search[value]');
              $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
              $dir = $this->request->getGet('order[0][dir]');
             */

            $datos = $this->pacientes->select('id,nombres,apellidos,dni,telefono,correoElectronico,created_at,updated_at')->where('deleted_at', null);
// $resultado = $this->bitacora->findAll();
// $this->bitacora->getResource()->countAllResults(),
// $this->bitacora->getResource($search)->countAllResults()
//      var_dump($datos);


            return \Hermawan\DataTables\DataTable::of($datos)->add('action', function ($row) {
                                return " <div class=\"btn-group\">
                          
                  <button class=\"btn btn-warning btnEditarPaciente\" data-toggle=\"modal\" idPaciente=\"$row->id\" data-target=\"#modalAgregarPaciente\">  <i class=\" fa fa-edit \"></i></button>
                  <button class=\"btn btn-danger btnEliminarPaciente\" idPaciente=\"$row->id\"><i class=\"fa fa-times\"></i></button></div>";
                            }, 'last')
                            ->toJson();
        }

        $titulos["title"] = lang('patients.title');
        $titulos["subtitle"] = lang('patients.subtitle');
        
       
//$data["data"] = $datos;
        return view('pacientes', $titulos);
    }

    /*
     * Lee paciente
     */

    public function traePaciente() {


        $idPaciente = $this->request->getPost("idPaciente");
        $datosPaciente = $this->pacientes->find($idPaciente);

        echo json_encode($datosPaciente);
    }

    /*
     * Guarda o actualiza paciente
     */

    public function guardar() {


        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;

        $datos = $this->request->getPost();

        if ($datos["idPaciente"] == 0) {


            try {


                if ($this->pacientes->save($datos) === false) {

                    $errores = $this->pacientes->errors();

                    foreach ($errores as $field => $error) {

                        echo $error . " ";
                    }

                    return;
                }

                $datosBitacora["descripcion"] = "Se guardo el paciente con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;

                $this->bitacora->save($datosBitacora);

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            if ($this->pacientes->update($datos["idPaciente"], $datos) == false) {

                $errores = $this->pacientes->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }

                return;
            } else {

                $datosBitacora["descripcion"] = "Se actualizo el paciente con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;
                $this->bitacora->save($datosBitacora);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;
    }

    public function delete($id) {
        if (!$found = $this->pacientes->delete($id)) {
            return $this->failNotFound(lang('patients.msg.msg_get_fail'));
        }

        $infoPaciente = $this->pacientes->find($id);

        $datosBitacora["descripcion"] = "Se elimino el paciente que contenia los siguientes datos " . json_encode($infoPaciente);

        $this->bitacora->save($datosBitacora);
        return $this->respondDeleted($found, lang('patients.msg.msg_delete'));
    }

    /**
     * Trae en formato JSON los pacientes para el select2
     * @return type
     */
    public function traerPacientesAjax() {
        
        $request = service('request');
        $postData = $request->getPost();

        $response = array();

        // Read new token and assign in $response['token']
        $response['token'] = csrf_hash();

        if (!isset($postData['searchTerm'])) {
            // Fetch record
            $pacientes = new PacientesModel();
            $listaPacientes = $pacientes->select('id,nombres,apellidos')
                    ->orderBy('nombres')
                    ->findAll(10);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record
            $pacientes = new PacientesModel();
            $listaPacientes = $pacientes->select('id,nombres,apellidos')
                    ->where("deleted_at",null)
                    ->like('nombres', $searchTerm)
                    ->orLike('apellidos', $searchTerm)
                    ->orderBy('nombres')
                    ->findAll(10);
        }

        $data = array();
        foreach ($listaPacientes as $paciente) {
            $data[] = array(
                "id" => $paciente['id'],
                "text" => $paciente['nombres'].' '.$paciente['apellidos'],
            );
        }

        $response['data'] = $data;

        return $this->response->setJSON($response);
    }

}

En Views creamos el archivo de la vista

<?= $this->include('agungsugiarto\boilerplate\Views\load\select2') ?>
<?= $this->include('agungsugiarto\boilerplate\Views\load\datatables') ?>
<?= $this->include('agungsugiarto\boilerplate\Views\load\nestable') ?>
<!-- Extend from layout index -->
<?= $this->extend('agungsugiarto\boilerplate\Views\layout\index') ?>

<!-- Section content -->
<?= $this->section('content') ?>

<?= $this->include('pacientesModulos\modalCaptura') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

                <button class="btn btn-primary btnAgregarPaciente" data-toggle="modal" data-target="#modalAgregarPaciente"><i class="fa fa-plus"></i>

                    <?= lang('patients.add') ?>

                </button>

            </div>
        </div>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-md-12">
                <div class="table-responsive">
                    <table id="tablaPacientes" class="table table-striped table-hover va-middle tablaPacientes">
                        <thead>
                            <tr>



                                <th>#</th>
                                <th><?= lang('patients.names') ?></th>
                                <th><?= lang('patients.lastName') ?></th>
                                <th><?= lang('patients.dni') ?></th>
                                <th><?= lang('patients.phone') ?></th>
                                <th><?= lang('patients.email') ?></th>
                                <th><?= lang('patients.createdAt') ?></th>
                                <th><?= lang('patients.updateAt') ?></th>
                                <th>Acciones </th>


                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.card -->

<?= $this->endSection() ?>




<?= $this->section('js') ?>
<script>

    /**
     * Cargamos la tabla
     */


    function cargaTabla() {



         $('.tablaPacientes').DataTable({
         "ajax": "<?= route_to('admin/pacientes') ?>",
         "deferRender": true,
         "serverSide": true,
         "retrieve": true,
         "processing": true
         
         });
        
    }


    cargaTabla();



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tablaPacientes").on("click", ".btnEditarPaciente", function () {

        var idPaciente = $(this).attr("idPaciente");


        console.log("paciente ", idPaciente);

        var datos = new FormData();
        datos.append("idPaciente", idPaciente);

        $.ajax({

            url: "<?= route_to('admin/pacientes/traerPaciente') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idPaciente").val(respuesta["id"]);
                $("#nombres").val(respuesta["nombres"]);
                $("#apellidos").val(respuesta["apellidos"]);
                $("#correoElectronico").val(respuesta["correoElectronico"]);
                $("#telefono").val(respuesta["telefono"]);
                $("#dni").val(respuesta["dni"]);

            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tablaPacientes").on("click", ".btnEliminarPaciente", function () {

        var idPaciente = $(this).attr("idPaciente");


        console.log("eliminar");

        Swal.fire({
            title: '<?= lang('boilerplate.global.sweet.title') ?>',
            text: "<?= lang('boilerplate.global.sweet.text') ?>",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: '<?= lang('boilerplate.global.sweet.confirm_delete') ?>'
        })
                .then((result) => {
                    if (result.value) {
                        $.ajax({
                            url: `<?= route_to('admin/pacientes') ?>/` + idPaciente,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });

                           $(".tablaPacientes").DataTable().destroy();
                            cargaTabla();
                            //tableUser.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })




</script>
<?= $this->endSection() ?>

En views creamos la carpeta pacientesModulos y dentro de esa capeta creamos el archivo modalCaptura.php con la siguiente código

<!-- Modal Pacientes -->
<div class="modal fade" id="modalAgregarPaciente" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"><?= lang('patients.createEdit') ?></h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <form id="form-paciente" class="form-horizontal">
                    <input type="hidden" id="idPaciente" name="idPaciente" value="0">



                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('patients.names') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="text" name="nombres" id="nombres" class="form-control <?= session('error.nombres') ? 'is-invalid' : '' ?>" value="<?= old('nombres') ?>" placeholder="<?= lang('patients.names') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('patients.lastName') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="text" name="apellidos"  id="apellidos"class="form-control <?= session('error.apellidos') ? 'is-invalid' : '' ?>" value="<?= old('apellidos') ?>" placeholder="<?= lang('patients.lastName') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="dni" class="col-sm-2 col-form-label"><?= lang('patients.dni') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-credit-card"></i></span>
                                </div>
                                <input type="text" name="dni"  id="dni" class="form-control <?= session('error.dni') ? 'is-invalid' : '' ?>" value="<?= old('dni') ?>" placeholder="<?= lang('patients.dni') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="phone" class="col-sm-2 col-form-label"><?= lang('patients.phone') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-mobile"></i></span>
                                </div>
                                <input type="text" name="telefono" id="telefono" class="form-control <?= session('error.phone') ? 'is-invalid' : '' ?>" value="<?= old('telefono') ?>" placeholder="<?= lang('patients.phone') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="correoElectronico" class="col-sm-2 col-form-label"><?= lang('patients.email') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-envelope"></i></span>
                                </div>
                                <input type="text" name="correoElectronico" id="correoElectronico" class="form-control <?= session('error.correoElectronico') ? 'is-invalid' : '' ?>" value="<?= old('correoElectronico') ?>" placeholder="<?= lang('patients.email') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal"><?= lang('boilerplate.global.close') ?></button>
                <button type="button" class="btn btn-primary btn-sm" id="btnGuardarPaciente"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

<?= $this->section('js') ?>


<script>

    $(document).on('click', '.btnAgregarPaciente', function (e) {

        console.log("asdasd");
        $(".form-control").val("");

        $("#idPaciente").val("0");
        
           $("#btnGuardarPaciente").removeAttr("disabled");

    })

    /* 
     * AL hacer click al editar
     */



    $(document).on('click', '.btnEditarPaciente', function (e) {


        var idPaciente = $(this).attr("idPaciente");

        //LIMPIAMOS CONTROLES
        $(".form-control").val("");

        $("#idPaciente").val(idPaciente);
        $("#btnGuardarPaciente").removeAttr("disabled");

    })

    /**
     * Guardar paciente
     */

    $(document).on('click', '#btnGuardarPaciente', function (e) {

        var idPaciente = $("#idPaciente").val();
        var nombres = $("#nombres").val();
        var apellidos = $("#apellidos").val();
        var telefono = $("#telefono").val();
        var dni = $("#dni").val();
        var correoElectronico = $("#correoElectronico").val();

        var ajaxPaciente = "ajaxPaciente";


        $("#btnGuardarPaciente").attr("disabled", true);


        var datos = new FormData();
        datos.append("idPaciente", idPaciente);
        datos.append("nombres", nombres);
        datos.append("apellidos", apellidos);
        datos.append("telefono", telefono);
        datos.append("dni", dni);
        datos.append("correoElectronico", correoElectronico);

        $.ajax({

            url: "<?= route_to('admin/pacientes/guardar') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            //dataType:"json",
            success: function (respuesta) {


                if (respuesta.match(/Correctamente.*/)) {


                    Toast.fire({
                        icon: 'success',
                        title: "Guardado Correctamente"
                    });


                    $('.tablaPacientes').DataTable().destroy();
                    cargaTabla();
                    $("#btnGuardarPaciente").removeAttr("disabled");

                   
                     $('#modalAgregarPaciente').modal('hide');
                } else {

                    Toast.fire({
                        icon: 'error',
                        title: respuesta
                    });

                    $("#btnGuardarPaciente").removeAttr("disabled");
                  //  $('#modalAgregarPaciente').modal('hide');

                }

            }

        }

        )




    });
</script>


<?= $this->endSection() ?>

Ahora configuramos las rutas en app/config/routes.php

    $routes->resource('pacientes', [
        'filter' => 'permission:pacientes-permiso',
        'controller' => 'PacientesController',
        'except' => 'show'
    ]);

    $routes->post('pacientes/guardar', 'PacientesController::guardar');

    $routes->post('pacientes/traerPaciente', 'PacientesController::traePaciente');

    $routes->post('pacientes/traerPacientesAjax', 'PacientesController::traerPacientesAjax');

En languaje en la carpeta es creamos el archivo patients con el siguiente codigo

<?php

$patients["names"] = "Firs Name";
$patients["lastName"] = "Last Name";
$patients["dni"] = "CARD ID";
$patients["phone"] = "Phone";
$patients["email"] = "E-Mail";
$patients["createdAt"] = "Date Creation";
$patients["updateAt"] = "Date Update";
$patients["add"] = "Add Patient";
$patients["actions"] = "Actions";
$patients["createEdit"] = "Create  / Edit Patient";
$patients["title"] = "Patient";
$patients["subtitle"] = "List of Patients";

$patients["msg_delete"] = "Patient has deleted .";
$patients["msg_get_fail"] = "The patient not exist or has deleted.";





return $patients;

Y en la carpeta es de español creamos el archivo patients con el siguiente código

<?php

$patients["names"] = "Nombres";
$patients["lastName"] = "Apellidos";
$patients["dni"] = "INE";
$patients["phone"] = "Telefono";
$patients["email"] = "Correo Electronico";
$patients["add"] = "Agregar Paciente";
$patients["updateAt"] = "Fecha Modificacion";
$patients["actions"] = "Acciones";
$patients["createEdit"] = "Crear / Editar Paciente";
$patients["title"] = "Pacientes";
$patients["subtitle"] = "Lista de Pacientes";

$patients["msg_delete"] = "El paciente ha sido eliminado correctamente.";
$patients["msg_get_fail"] = "El paciente no existe o fue eliminado.";




return $patients;
Y listo ya tenemos nuestro CRUD de pacientes

Comprimir y generar Respaldo en Windows automáticamente a una hora determinada

Para respaldar información importante como base de datos, documentos ETC, en muchos lugares se opta por las nubes como Google Drive, Dropbox ETC

Pero en ocasiones simplemente se opta por comprimir y pasarlo a otro equipo de computo

Para ello normalmente se usa cobian pero en una ocasión en nuestro caso falló y no alcanzaba a copiarse el archivo completo

Así que mostraremos como hacer el comando para que comprima y copie el archivo aunque exista problemas de intermitencia en la red

Para comprimir es necesario tener instalado winRar, ya instalado para comprimir usamos el siguiente comando, en este caso copiamos el respaldo de la base de datos de SQL Server

"C:\Program Files"\WinRAR\rar a -r -rr -v4700M "C:\respaldoDB\Respaldo%date:~-4,4%-%date:~-7,2%-%date:~-10,2%.rar" "C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\Backup\"

Una vez comprimido se procede a realizar la copia en el otro equipo para ello usamos el siguiente comando, con robocopy en caso de que se caiga la red vuelve a intentar a copiarlo hasta que lo copia bien y completamente

robocopy "C:\respaldoDB" "\\IPDELOTROEQUIPO\Respaldo de prueba\carpetadestino"

Y procedemos a borrar el archivo creado en el equipo local

DEL /F /A C:\respaldoDB\Respaldo_%date:~-4,4%-%date:~-7,2%-%date:~-10,2%.rar

  

al final el archivo bat quedaría de la siguiente forma

"C:\Program Files"\WinRAR\rar a -r -rr -v4700M "C:\respaldoDB\Respaldo%date:~-4,4%-%date:~-7,2%-%date:~-10,2%.rar" "C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\Backup\"

  

robocopy "C:\respaldoDB" "\\IPDELOTROEQUIPO\Respaldo de prueba\carpetadestino"

  

DEL /F /A C:\respaldoDB\Respaldo_%date:~-4,4%-%date:~-7,2%-%date:~-10,2%.rar

  

Ya listo para ejecutarlo a la hora que sea automáticamente con el programador de tareas de windows

Como convertir Web hecha en PHP8 con FrameWork CodeIgniter 4 en una APP de Escritorio

code

git clone https://github.com/electron/electron-quick-start.git

en el archivo package.json lo dejamos de la siguiente manera

	{

	"build": {
	"appId": "com.electron.example",
	"compression": "maximum"
	},
	"name": "XML2PDF CFDI",

	"version": "2.0.0",

	"description": "Una pequeña aplicación para convertir el CFDI XML a PDF",

	"main": "main.js",

	"compression": "maximum",

	"scripts": {

	"***": null,
	"pro:windows": "electron-builder --win"

	},
	"repository": "https://github.com/electron/electron-quick-start",  

	"keywords": [
	"CFDI",
	"XML",
	"PDF",
	"tutorial"
	],
	"author": "Julio Cesar Leyva Rodriguez",
	"license": "CC0-1.0",
	"devDependencies": {
	"electron": "^21.2.3",
	"electron-builder": "^22.14.13",
	"electron-packager": "^17.1.1"
	},
	"dependencies": {
	"modclean": "^3.0.0-beta.1"
	}
	}

en main.js lo dejamos haci lo que hara es cargar la url y levantar el servicio de php

	// Modules to control application life and create native browser window

	const {app, BrowserWindow} = require('electron')

	const path = require('path')

	const exec = require('child_process').exec;

	  
	  

	function execute(command, callback) {

	exec(command, (error, stdout, stderr) => {

	callback(stdout);

	});

	};

	  
	  

	// call the function

	execute('Ejecutar.bat', (output) => {

	console.log(output);

	});

	  

	function createWindow () {

	// Create the browser window.

	const mainWindow = new BrowserWindow({

	width: 800,

	height: 600,

	webPreferences: {

	preload: path.join(__dirname, 'preload.js')

	}

	})

	  

	// and load the index.html of the app.

	mainWindow.setMenu(null)

	mainWindow.loadURL('http://localhost:8097')

	  

	// Open the DevTools.

	// mainWindow.webContents.openDevTools()

	}

	  

	// This method will be called when Electron has finished

	// initialization and is ready to create browser windows.

	// Some APIs can only be used after this event occurs.

	app.whenReady().then(() => {

	createWindow()

	  

	app.on('activate', function () {

	// On macOS it's common to re-create a window in the app when the

	// dock icon is clicked and there are no other windows open.

	if (BrowserWindow.getAllWindows().length === 0) createWindow()

	})

	})

	  

	// Quit when all windows are closed, except on macOS. There, it's common

	// for applications and their menu bar to stay active until the user quits

	// explicitly with Cmd + Q.

	app.on('window-all-closed', function () {

	if (process.platform !== 'darwin') app.quit()

	})

	  

	// In this file you can include the rest of your app's specific main process

	// code. You can also put them in separate files and require them here.

ahora posicionados en la carpeta \electron-quick-start> corremos el siguiente comando

npm install

Después creamos el paquete-ejecutable con el siguiente comando

electron-packager . -platform=win32 -arch=x64 ConversorXMLPDF

y nos genera los archivos necesarios
image

Creamos una carpeta en C: llamada aplicación o como ustedes quieran llamarle y copiamos el contenido, la carpeta del proyecto del codeigniter y los archivos ejecutable de PHP con los que trabajan, pueden copiar los del xampp y con el que debugean a través de netbeans
quedaría de la siguiente forma

image

ConvertidorXMLPDF seria el proyecto en codeigniter, php8 son los binarios necesarios para ejecutar código php, este lo pueden extraer de xampp y lo demas son los archivos de electronJS

Ahora en el archivo de configuración de código igniter en config/app.php en la base URL la dejamos asi

public $baseURL = '[http://localhost:8097/](http://localhost:8097/)';

El archivo Ejecutar.bat es el que va iniciar todo el codeigniter y dentro de el va el siguiente codigo

@echo off

php8\php.exe convertidorXMLPDF/spark serve --port 8097

El archivo Ejecutar.bat se ejecuta automáticamente al abrir el ejecutable

Y listo ahora solo queda hacer el instalador pero lo dejamos para otro manual.

Crear un Conversor de Facturas XML Mexicanas a PDF en CodeIgniter4

En este pequeño tutorial vamos a mostrar como crear rápidamente un conversor XML a PDF de las facturas electrónicas Mexicanas
Requisitos

  1. Tener instalado la versión mas reciente de Composer
  2. Conocimiento básico de PHP y Programación
  3. Ganas de aprender

Paso uno creamos el proyecto en CodeIgniter 4

composer create-project codeigniter4/appstarter convertidorXMLPDF

Actualizamos

composer update

Instalamos de composer el paquete que convierte el xml PDF

composer require phpcfdi/cfditopdf

creamos el archivo FileUpload.php en controladores

<?php 
namespace App\Controllers;
// use App\Models\FormModel;
use CodeIgniter\Controller;
class FileUpload extends Controller
{
    public function index() 
    {
        return view('home');
    }
    function upload() { 
        helper(['form', 'url','filesystem']);



 
            $xml = $this->request->getFile('file');
           /* $img->move(WRITEPATH . 'uploads');
    
            $data = [
               'name' =>  $img->getName(),
               'type'  => $img->getClientMimeType()
            ];
    
            $save = $db->insert($data);
            print_r('File has successfully uploaded');     */


         //  echo $_FILES['file']['tmp_name'];
            $string = file_get_contents($_FILES['file']['tmp_name']);

         
            // clean cfdi
            $xml = \PhpCfdi\CfdiCleaner\Cleaner::staticClean($string);

            
         
            // create the main node structure
            $comprobante = \CfdiUtils\Nodes\XmlNodeUtils::nodeFromXmlString($xml);

            // create the CfdiData object, it contains all the required information
            $cfdiData = (new \PhpCfdi\CfdiToPdf\CfdiDataBuilder())
                ->build($comprobante);

            // create the converter
            $converter = new \PhpCfdi\CfdiToPdf\Converter(
                new \PhpCfdi\CfdiToPdf\Builders\Html2PdfBuilder()
            );

            // create the invoice as output.pdf
            $converter->createPdfAs($cfdiData,  'output.pdf');
            
                $filename = WRITEPATH . 'uploads\output.pdf';
                
                echo '
                <script type="text/javascript">
                    window.open("/output.pdf" , "_blank");
                  </script>';
                
             
               return view('welcome_message');
                  
                
        }
     
    
}

*Agregamos el formulario en las vistas en este caso al archivo welcome_message.php

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Conversor CFDI!</title>
        <meta name="description" content="Pequeño y poderoso conversor de CFDI XML a PDF hecho en CodeIgnter">
        <meta name="keywords" content="CFDI, CFDI 4.0, CFDI 3.3, Representación Impresa, CodeIgniter, Factura Electrónica"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="shortcut icon" type="image/png" href="/favicon.ico"/>

        <!-- STYLES -->

        <style {csp-style-nonce}>
            * {
                transition: background-color 300ms ease, color 300ms ease;
            }
            *:focus {
                background-color: rgba(221, 72, 20, .2);
                outline: none;
            }
            html, body {
                color: rgba(33, 37, 41, 1);
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
                font-size: 16px;
                margin: 0;
                padding: 0;
                -webkit-font-smoothing: antialiased;
                -moz-osx-font-smoothing: grayscale;
                text-rendering: optimizeLegibility;
            }
            header {
                background-color: rgba(247, 248, 249, 1);
                padding: .4rem 0 0;
            }
            .menu {
                padding: .4rem 2rem;
            }
            header ul {
                border-bottom: 1px solid rgba(242, 242, 242, 1);
                list-style-type: none;
                margin: 0;
                overflow: hidden;
                padding: 0;
                text-align: right;
            }
            header li {
                display: inline-block;
            }
            header li a {
                border-radius: 5px;
                color: rgba(0, 0, 0, .5);
                display: block;
                height: 44px;
                text-decoration: none;
            }
            header li.menu-item a {
                border-radius: 5px;
                margin: 5px 0;
                height: 38px;
                line-height: 36px;
                padding: .4rem .65rem;
                text-align: center;
            }
            header li.menu-item a:hover,
            header li.menu-item a:focus {
                background-color: rgba(221, 72, 20, .2);
                color: rgba(221, 72, 20, 1);
            }
            header .logo {
                float: left;
                height: 44px;
                padding: .4rem .5rem;
            }
            header .menu-toggle {
                display: none;
                float: right;
                font-size: 2rem;
                font-weight: bold;
            }
            header .menu-toggle button {
                background-color: rgba(221, 72, 20, .6);
                border: none;
                border-radius: 3px;
                color: rgba(255, 255, 255, 1);
                cursor: pointer;
                font: inherit;
                font-size: 1.3rem;
                height: 36px;
                padding: 0;
                margin: 11px 0;
                overflow: visible;
                width: 40px;
            }
            header .menu-toggle button:hover,
            header .menu-toggle button:focus {
                background-color: rgba(221, 72, 20, .8);
                color: rgba(255, 255, 255, .8);
            }
            header .heroe {
                margin: 0 auto;
                max-width: 1100px;
                padding: 1rem 1.75rem 1.75rem 1.75rem;
            }
            header .heroe h1 {
                font-size: 2.5rem;
                font-weight: 500;
            }
            header .heroe h2 {
                font-size: 1.5rem;
                font-weight: 300;
            }
            section {
                margin: 0 auto;
                max-width: 1100px;
                padding: 2.5rem 1.75rem 3.5rem 1.75rem;
            }
            section h1 {
                margin-bottom: 2.5rem;
            }
            section h2 {
                font-size: 120%;
                line-height: 2.5rem;
                padding-top: 1.5rem;
            }
            section pre {
                background-color: rgba(247, 248, 249, 1);
                border: 1px solid rgba(242, 242, 242, 1);
                display: block;
                font-size: .9rem;
                margin: 2rem 0;
                padding: 1rem 1.5rem;
                white-space: pre-wrap;
                word-break: break-all;
            }
            section code {
                display: block;
            }
            section a {
                color: rgba(221, 72, 20, 1);
            }
            section svg {
                margin-bottom: -5px;
                margin-right: 5px;
                width: 25px;
            }
            .further {
                background-color: rgba(247, 248, 249, 1);
                border-bottom: 1px solid rgba(242, 242, 242, 1);
                border-top: 1px solid rgba(242, 242, 242, 1);
            }
            .further h2:first-of-type {
                padding-top: 0;
            }
            footer {
                background-color: rgba(221, 72, 20, .8);
                text-align: center;
            }
            footer .environment {
                color: rgba(255, 255, 255, 1);
                padding: 2rem 1.75rem;
            }
            footer .copyrights {
                background-color: rgba(62, 62, 62, 1);
                color: rgba(200, 200, 200, 1);
                padding: .25rem 1.75rem;
            }
            @media (max-width: 629px) {
                header ul {
                    padding: 0;
                }
                header .menu-toggle {
                    padding: 0 1rem;
                }
                header .menu-item {
                    background-color: rgba(244, 245, 246, 1);
                    border-top: 1px solid rgba(242, 242, 242, 1);
                    margin: 0 15px;
                    width: calc(100% - 30px);
                }
                header .menu-toggle {
                    display: block;
                }
                header .hidden {
                    display: none;
                }
                header li.menu-item a {
                    background-color: rgba(221, 72, 20, .1);
                }
                header li.menu-item a:hover,
                header li.menu-item a:focus {
                    background-color: rgba(221, 72, 20, .7);
                    color: rgba(255, 255, 255, .8);
                }
            }
        </style>
    </head>
    <body>

        <!-- HEADER: MENU + HEROE SECTION -->
        <header>

            <div class="menu">
                <ul>
                    <li class="logo">
                        <a href="https://cesarsystems.com.mx/" target="_blank">
                            <img src="<?php echo base_url();?>/logo.png"
                                 aria-label="Visita mi pagina oficial para mas programas y utilidades!"/></img>
                        </a>
                    </li>
                    <li class="menu-toggle">
                        <button onclick="toggleMenu();">&#9776;</button>
                    </li>
                    <li class="menu-item hidden"><a href="#">Inicio</a></li>
                    <li class="menu-item hidden"><a href="https://cesarsystems.com.mx/" target="_blank">Descargas</a>
                    </li>
                    <li class="menu-item hidden"><a href="https://www.youtube.com/c/rasec555" target="_blank">Comunidad</a></li>
                    <li class="menu-item hidden"><a
                            href="https://patreon.com/user?u=74078772&utm_medium=clipboard_copy&utm_source=copyLink&utm_campaign=creatorshare_creator" target="_blank">Patrocinar</a>
                    </li>
                </ul>
            </div>

            <div class="heroe">

                <h1>Bienvenido a Conversor XML A PDF V2</h1>

                <h2>Un simple software para convertir los CFDI XML a PDF</h2>

            </div>

        </header>

        <!-- CONTENT -->

        <section>

            <h1>Acerca de este programa</h1>

            <p>Este programa toma los archivos XML de la factura 3.3 o 4.0 para convertirlos a una representación generica impresa de un Comprobante Fiscal Digital</p>

            <p>Para convertir el XML a PDF puede arrastrar el archivo XML en el Boton llamado "Seleccionar archivo" </p>

            <p>En caso de que no salga el PDF asegurate de no tener bloqueado el popup o desbloquealo para esta pagina ya que el pdf saldra en el popup" </p>



            <form method="post" action="<?php echo base_url('FileUpload/upload'); ?>" enctype="multipart/form-data">
                <div class="form-group">
                    <label>Seleccione el archivo XML </label>
                    <input type="file" name="file" id="file"  accept=".xml" class="form-control">
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-danger">Generar PDF</button>
                </div>
            </form>

        </section>

        <div class="further">

            <section>

                <h1>¡Ir por más!</h1>

                <h2>
                    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><rect x='32' y='96' width='64' height='368' rx='16' ry='16' style='fill:none;stroke:#000;stroke-linejoin:round;stroke-width:32px'/><line x1='112' y1='224' x2='240' y2='224' style='fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px'/><line x1='112' y1='400' x2='240' y2='400' style='fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px'/><rect x='112' y='160' width='128' height='304' rx='16' ry='16' style='fill:none;stroke:#000;stroke-linejoin:round;stroke-width:32px'/><rect x='256' y='48' width='96' height='416' rx='16' ry='16' style='fill:none;stroke:#000;stroke-linejoin:round;stroke-width:32px'/><path d='M422.46,96.11l-40.4,4.25c-11.12,1.17-19.18,11.57-17.93,23.1l34.92,321.59c1.26,11.53,11.37,20,22.49,18.84l40.4-4.25c11.12-1.17,19.18-11.57,17.93-23.1L445,115C443.69,103.42,433.58,94.94,422.46,96.11Z' style='fill:none;stroke:#000;stroke-linejoin:round;stroke-width:32px'/></svg>
                    Descargas y tutoriales
                </h2>

                <p>Puedes encontrar mas aplicativos, manuales y tutoriales en la siguiente pagina <a href="https://cesarsystems.com.mx/"
                                                target="_blank">Pagina Principal</a> !</p>

                <h2>
                    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><path d='M431,320.6c-1-3.6,1.2-8.6,3.3-12.2a33.68,33.68,0,0,1,2.1-3.1A162,162,0,0,0,464,215c.3-92.2-77.5-167-173.7-167C206.4,48,136.4,105.1,120,180.9a160.7,160.7,0,0,0-3.7,34.2c0,92.3,74.8,169.1,171,169.1,15.3,0,35.9-4.6,47.2-7.7s22.5-7.2,25.4-8.3a26.44,26.44,0,0,1,9.3-1.7,26,26,0,0,1,10.1,2L436,388.6a13.52,13.52,0,0,0,3.9,1,8,8,0,0,0,8-8,12.85,12.85,0,0,0-.5-2.7Z' style='fill:none;stroke:#000;stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px'/><path d='M66.46,232a146.23,146.23,0,0,0,6.39,152.67c2.31,3.49,3.61,6.19,3.21,8s-11.93,61.87-11.93,61.87a8,8,0,0,0,2.71,7.68A8.17,8.17,0,0,0,72,464a7.26,7.26,0,0,0,2.91-.6l56.21-22a15.7,15.7,0,0,1,12,.2c18.94,7.38,39.88,12,60.83,12A159.21,159.21,0,0,0,284,432.11' style='fill:none;stroke:#000;stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px'/></svg>
                    Grupos
                </h2>

                <p>Puede seguirnos en los siguientes grupos y redes sociales como <a href="https://www.youtube.com/c/rasec555"
                                      target="_blank">Canal de youtube </a>, 
                                      
                                      <a href="https://t.me/CesarSystems"
                                      target="_blank">Canal de Telegram</a> ,
                                      
                                      <a href="https://twitter.com/JulioLeyvaR"
                                      target="_blank">Twitter</a> ,
                                      
                                       <a href="https://odysee.com/@JulioCesarLeyvaRodriguez:9"
                                      target="_blank">Odysee</a> y
                                      
                                      <a href="https://www.facebook.com/rasec555/"
                                      target="_blank">Facebook</a> 
                
                
                </p>

                <h2>
                    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'><line x1='176' y1='48' x2='336' y2='48' style='fill:none;stroke:#000;stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px'/><line x1='118' y1='304' x2='394' y2='304' style='fill:none;stroke:#000;stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px'/><path d='M208,48v93.48a64.09,64.09,0,0,1-9.88,34.18L73.21,373.49C48.4,412.78,76.63,464,123.08,464H388.92c46.45,0,74.68-51.22,49.87-90.51L313.87,175.66A64.09,64.09,0,0,1,304,141.48V48' style='fill:none;stroke:#000;stroke-linecap:round;stroke-miterlimit:10;stroke-width:32px'/></svg>
                    Patrocinar
                </h2>

                <p>Puedes apoyar nuestros proyectos en Patreon y obtendras acceso exclusivo a los fuentes mas recientes
                    <a href="https://patreon.com/user?u=74078772&utm_medium=clipboard_copy&utm_source=copyLink&utm_campaign=creatorshare_creator" target="_blank">
                        Unete</a> </p>

            </section>

        </div>

        <!-- FOOTER: DEBUG INFO + COPYRIGHTS -->

        <footer>
            <div class="environment">

                <p>Tiempo de Renderizado {elapsed_time} seconds</p>

            

            </div>

            <div class="copyrights">

                <p>&copy; <?= date('Y') ?> Conversor XML a PDF es un proyecto de código abierto lanzado con la licencia MIT
                    open source licence.</p>

            </div>

        </footer>

        <!-- SCRIPTS -->

        <script>
            function toggleMenu() {
                var menuItems = document.getElementsByClassName('menu-item');
                for (var i = 0; i < menuItems.length; i++) {
                    var menuItem = menuItems[i];
                    menuItem.classList.toggle("hidden");
                }
            }
        </script>

        <!-- -->

    </body>
</html>

Y por último agregamos esto en las rutas

$routes->get('/', 'FileUpload::index');
$routes->match(['get', 'post'], 'FileUpload/upload', 'FileUpload::upload');

y listo pueden ver un ejemplo de como quedo en el siguiente enlace https://xml2pdf.cesarsystems.com.mx/

Novedades MX Linux 21 WILDFLOWER

Ha salido hace ya hace un poco de tiempo la versión 21 de MX LINUX, la distribución que se mantiene en el primer lugar en la top 100 de la WEB distrowatch así que vamos a dedicarle un post, sobre todo especificando la versión que viene con el entorno de escritorio XFCE

Para resumir rápidamente les dejo las novedades que tiene esta versión

Como publicar proyecto de gambas3 en la granja de software

Suponiendo que ya esta listo el programa lo que sigue puede ser lo siguiente
1. Crear un simple Ejecutable
2. Crear un paquete de instalación para instalarlo en cualquier distribución de Linux a través de un empaquetado .deb
3. Publicarlo en la granja de software de Gambas3

Lo que haremos es publicarlo en la granja de software

Primero le asignamos un icono al proyecto en las propiedades del proyecto

Aprovechamos para escribir la descripción del proyecto y los autores

Ahora nos vamos al menú Proyecto -> Publicar

Le rellenamos mas datos en caso de ser necesario
Elegimos las etiquetas
Elegimos una captura del programa, esto de mostrara cuando alguien quiera descargar nuestro programa
Elegimos las dependencias
Elegimos si publicarlo con nuestro usuario, aunque se puede publicar bajo el anonimato sin crear usuario en gambasfarm.org

Finalmente le damos Publicar y mostrara el siguiente mensaje comunicándonos de que el proyecto se ha publicado correctamente
Si nos vamos a la granja de software ya estará publicado nuestro proyecto

Como guardar archivos en disco duro con GAMBAS3 File.Save

Ya tenemos las funciones para crear automáticamente los códigos del Modelo/Vista/Controlador para PHP ahora lo que sigue es crear los archivos en el directorio del directorio del proyecto en PHP

Para guardar archivo en Gambas3 usaremos la función File.Save(“ruta donde se va a guardar”,”contenido del archivo”)

Pasos

Le damos doble click al botton para generar el evento _Click
Quedara de la siguiente forma

Insertamos en ese evento el siguiente código

  Dim strModel1 As String
  Dim strControlador As String
  Dim strVista As String 
  
  ' GUARDAR CONFIGURACION

  
  Settings["ruta"] = ruta.SelectedPath
  Settings["tabla"] = txtTabla.Text
  
  ' CREA ARCHIVO DEL MODELO
  strModel1 = creadorModelo.creaModelo()
   
  File.Save(ruta.SelectedPath & "/" & "modelos/" & txtTabla.Text & ".modelo.php", strModel1)
  
  ' CREA ARCHIVO DEL CONTROLADOR
  strControlador = creadorControlador.creaControlador()
  
  File.Save(ruta.SelectedPath & "/" & "controladores/" & txtTabla.Text & ".controlador.php", strControlador)
   
   
  ' CREA ARCHIVO VISTA 
  strVista = creadorVista.creaVista()
  
  File.Save(ruta.SelectedPath & "/" & "vistas/modulos/" & txtTabla.Text & ".php", strVista)
  
  ' MANDAMOS MENSAJE DE QUE TODO SE HA GUARDADO CORRECTAMENTE
  Message.Info("Guardado Correctamente")
Se observa como se usan las funciones para crear el código, por ejemplo, para crear el modelo se usa creadorModelo.creaModelo() y guarda todo el texto/código generado en la variable strModel1

Luego guarda con File.Save en la ruta que seleccionamos con el control llamado “ruta” el texto que esta en strModel

Esta misma lógica se usa para el controlador y la vista

En la siguiente publicación veremos como asignarle un icono al proyecto

Página 9 de 12

Creado con WordPress & Tema de Anders Norén