Herramientas Informaticas

Autor: juliocesar20200413 Página 13 de 139

Desarrollador web apasionado y gusto por la buena musica

Creando modulo de configuración de correo electrónico #04

Ya creamos el catalogo de empresas, ahora vamos a ir creando un modulo de configuración de correo electrónico

Primero creamos el archivo del modelo en app/Models/SettingsMailModel.php con el siguiente contenido

<?php

namespace App\Models;

use CodeIgniter\Model;

class SettingsMailModel extends Model
{
    protected $table      = 'mailsettings';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

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

    protected $allowedFields = ['id', 'email', 'host', 'smtpDebug', 'SMTPAuth', 'port', 'created_at', 'deleted_at', 'updated_at', 'smptSecurity', 'pass'];
}

Para la tabla donde se guardara la información corremos el siguiente código SQL en PHPMYADMIN en nuestra base de datos, en publicaciones posteriores les dejare los archivos de migración para que no tengan la necesidad de correr archivos SQL y sirva para diferentes motores de base de datos

-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 18-04-2023 a las 00:56:17
-- 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: `wheelsoft`
--

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

--
-- Estructura de tabla para la tabla `mailsettings`
--

CREATE TABLE `mailsettings` (
  `id` int(11) NOT NULL,
  `email` varchar(512) DEFAULT NULL,
  `host` varchar(128) DEFAULT NULL,
  `smtpDebug` varchar(16) DEFAULT NULL,
  `SMTPAuth` varchar(16) DEFAULT NULL,
  `port` varchar(16) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `smptSecurity` varchar(64) DEFAULT NULL,
  `pass` varchar(128) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

--
-- Volcado de datos para la tabla `mailsettings`
--

INSERT INTO `mailsettings` (`id`, `email`, `host`, `smtpDebug`, `SMTPAuth`, `port`, `created_at`, `deleted_at`, `updated_at`, `smptSecurity`, `pass`) VALUES
(1, 'mail@domain.com', 'mail.host5.com', '1', '1', '587', NULL, NULL, NULL, 'tls', 'password');

--
-- Índices para tablas volcadas
--

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

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `mailsettings`
--
ALTER TABLE `mailsettings`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
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 app/controllers/SettingsMailController.php con el siguiente contenido

<?php

namespace julio101290\boilerplate\Controllers\Users;

namespace App\Controllers;

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Controller;
use App\Models\SettingsMailModel;
use App\Models\LogModel;
use julio101290\boilerplate\Controllers\BaseController;
use julio101290\boilerplate\Entities\Collection;
use julio101290\boilerplate\Models\GroupModel;
use CodeIgniter\Config\Services;
use App\Models\RegisterModel;
use App\Controllers\RegisterController;
use PHPMailer\PHPMailer;

/**
 * Class UserController.
 */
class SettingsMailController extends BaseController
{

    use ResponseTrait;

    /** @var \agungsugiarto\boilerplate\Models\GroupModel */
    protected $group;
    protected $settingsMail;
    protected $log;
    protected $register;
    protected $registerController;
    protected $custumer;

    /** @var \agungsugiarto\boilerplate\Models\UserModel */
    protected $users;

    public function __construct()
    {
        $this->group = new GroupModel();
        $this->log = new LogModel();
        $this->settingsMail = new SettingsMailModel();
        $autorize = $this->authorize = Services::authorization();
        helper('menu');
    }

    public function index()
    {




        $datos = $this->settingsMail->where("id", 1)->first();

        $data["title"] = "Correo Electronicos";
        $data["subtitle"] = "Configuraciones de Correo Electronico";
        $data["data"] = $datos;

        return view('mailSettings', $data);
    }

    /** 
    public function sendMailPDF($uuid)
    {

        //DATOS CORREO
        $datos = $this->settingsMail->where("id", 1)->first();

        //DATOS  REGISTRO

        $register = $this->register->select("*")->where("uuid", $uuid)->first();

        $custumer = $this->custumer->select("*")->where("id", $register["custumer"])->first();

        $mailsTarjets = "";

        $correo = $datos["email"];
        $SMTPDebug = $datos["smtpDebug"];
        $host = $datos["host"];

        if ($datos["SMTPAuth"] == 1) {
            $SMTPAuth = true;
        } else {
            $SMTPAuth = false;
        }

        $puerto = $datos["port"];
        $clave = $datos["pass"];

        $SMTPSeguridad = $datos["smptSecurity"];

        // Load Composer's autoloader
        // Instantiation and passing `true` enables exceptions
        $mail = new PHPMailer\PHPMailer();

        try {


            //Server settings
            $mail->SMTPDebug = $SMTPDebug;                      // Enable verbose debug output
            $mail->isSMTP();                                            // Send using SMTP
            $mail->Host = $host;                    // Set the SMTP server to send through
            $mail->SMTPAuth = $SMTPAuth;                                   // Enable SMTP authentication
            $mail->Username = $correo;                     // SMTP username
            $mail->Password = $clave;                               // SMTP password
            $mail->SMTPSecure = $SMTPSeguridad;         // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` also accepted
            $mail->Port = $puerto;

            $nombreEmpresa = "";
            // TCP port to connect to
            //Recipients
            $mail->setFrom($correo, $nombreEmpresa);

            if ($custumer["email1"] != "") {
                try {
                    $mailAddress = $mail->addAddress($custumer["email1"], '');

                    if (!$mailAddress) {

                        echo "Error con el correo Electronico";
                        return;
                    }
                } catch (Exception $ex) {

                    echo $ex->getMessage();
                }
            }

            if ($custumer["email2"] != "") {

                try {
                    $mailAddress = $mail->addAddress($custumer["email2"], '');

                    if (!$mailAddress) {

                        echo "Error con el correo Electronico";
                        return;
                    }
                } catch (Exception $ex) {

                    echo $ex->getMessage();
                }
            }

            if ($custumer["email3"] != "") {

                try {
                    $mailAddress = $mail->addAddress($custumer["email3"], '');

                    if (!$mailAddress) {

                        echo "Error con el correo Electronico";
                        return;
                    }

                    if (!$mailAddress) {

                        echo "Error con el correo Electronico";
                        return;
                    }
                } catch (Exception $ex) {

                    echo $ex->getMessage();
                }
            }

            if ($custumer["email4"] != "") {

                try {
                    $mailAddress = $mail->addAddress($custumer["email4"], '');

                    if (!$mailAddress) {

                        echo "Error con el correo Electronico";
                        return;
                    }
                } catch (Exception $ex) {

                    echo $ex->getMessage();
                }
            }

            if ($custumer["email5"] != "") {

                try {
                    $mailAddress = $mail->addAddress($custumer["email5"], '');

                    if (!$mailAddress) {

                        echo "Error con el correo Electronico";
                        return;
                    }
                } catch (Exception $ex) {

                    echo $ex->getMessage();
                }
            }
            // Add a recipient
            //$mail->addReplyTo('info@example.com', 'Information');
            //mail->addCC('cc@example.com');
            //$mail->addBCC('bcc@example.com');
            // Attachments
            $attachment = $this->registerController->report($uuid, 1);
            $mail->AddStringAttachment($attachment, 'registro' . $register["codeCustumer"] . '.pdf', 'base64', 'application/pdf');

            // Add attachments
            //$mail->addAttachment('/tmp/image.jpg', 'new.jpg');    // Optional name
            // Content
            $mail->isHTML(true);                                  // Set email format to HTML
            $mail->Subject = "Envio de Registro";
            $mail->Body = "Adjuntamos el registro de verificación de neumaticos";
            $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

            try {
                $send = $mail->send();
            } catch (Exception $ex) {

                echo $ex->getMessage();
                return;
            }

            if ($send) {
                echo 'Correo Enviado Correctamente';
            } else {
                echo 'Error al enviar el correo';
            }
        } catch (Exception $e) {
            echo "Error al enviar el correo: {$e->ErrorInfo}";
        }
    }
*/
    public function guardar()
    {


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

        //GUARDA CONFIGURACIONES
        $this->settingsMail->update(1, $_POST);

        //  return redirect()->to("/admin/hospital");
        return redirect()->back()->with('sweet-success', 'Actualizado Correctamente');
        // return redirect()->back()->with('sweet-success','Guardado Correctamente');
    }
}

Creamos el archivo app/views/mailSettings.php con el siguiente contenido

<?= $this->extend('julio101290\boilerplate\Views\layout\index') ?>

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



<section class="content">
    <div class="container-fluid">
        <div class="row">

            <div class="col-md-12">

                <div class="card card-primary">
                    <div class="card-header">
                        <h3 class="card-title">Configuración Correo Electrónico</h3>
                    </div>



                    <form action="<?= base_url('admin/mailSettings') ?>/save" method="post">
                        <?= csrf_field() ?>

                        <div class="card-body">


                            <div class="form-group">
                                <label for="nombreEmpresa">E-Mail</label>
                                <input type="text" class="form-control" id="email" value="<?= $data["email"] ?>" name="email" placeholder="Inserte el Email">
                            </div>

                            <div class="form-group">
                                <label for="correoElectronico">Host</label>
                                <input type="text" class="form-control" value="<?= $data["host"] ?>" id="host" name="host" placeholder="Host">
                            </div>

                            <div class="form-group">
                                <label for="smtpDebug">Debug Client</label>
                                <select id="smtpDebug" name="smtpDebug">

                                    <option value="0">DEBUG_OFF</option>

                                    <option value="1">DEBUG_CLIENT</option>

                                    <option value="2">DEBUG_SERVER</option>

                                </select>

                            </div>

                            <div class="form-group">
                                <label for="SMTPAuth">SMTP Auth</label>
                                <select id="SMTPAuth" name="SMTPAuth">


                                    <option value=""></option>

                                    <option value="0">Sin autentificación</option>

                                    <option value="1">Con autentificación</option>


                                </select>

                            </div>


                            <div class="form-group">
                                <label for="smptSecurity">Seguridad</label>
                                <select id="smptSecurity" name="smptSecurity">


                                    <option value="">Sin Seguridad</option>
                                    <option value="ssl">Seguridad SSL</option>
                                    <option value="tls">Seguridad TLS</option>

                                </select>

                            </div>


                            <div class="form-group">
                                <label for="port">Puerto</label>
                                <input type="number" class="form-control" value="<?= $data["port"] ?>" id="port" name="port" placeholder="Puerto">
                            </div>

                            <div class="form-group">
                                <label for="port">Contraseña</label>
                                <input type="password" class="form-control" value="<?= $data["pass"] ?>" id="pass" name="pass" placeholder="Contraseña">
                            </div>


                        </div>

                        <div class="card-footer">
                            <button type="submit" class="btn btn-primary btnGuardar">Guardar</button>
                        </div>
                    </form>
                </div>





            </div>



        </div>

    </div>
</section>


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


<?= $this->section('js') ?>
<script>
    $("#smptSecurity").val("<?= $data["smptSecurity"] ?>");
    $("#SMTPAuth").val("<?= $data["SMTPAuth"] ?>");
    $("#smtpDebug").val("<?= $data["smtpDebug"] ?>");
</script>
<?= $this->endSection() ?>

en app/config/routes.php agregamos el siguiente codigo

 $routes->resource('emailSettings', [
        'filter' => 'permission:email-permiso',
        'controller' => 'SettingsMailController',
    ]);
    
    $routes->post('mailSettings/save', 'SettingsMailController::guardar');
    
    //Para futuros envios
    //$routes->get('mailSettings/sendMail/(:any)', 'SettingsMailController::sendMailPDF/$1');
Y listo ya tenemos nuestro modulo de configuración solo tenemos que ajustar unos permisos y roles
Video Demostrativo

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

CI4JCPOS Construyendo Menú #2

Ya tenemos la base del proyecto, ahora lo que sigue es darle forma creando los menús

Realmente aquí no hay nada complejo, el menú soporta 2 niveles, podemos escoger nuestro icono de font awesome le pones la ruta y que roles tendrán acceso a ese nivel, para acomodar el menú solo basta con arrastrar y soltar los items
Realmente aquí no hay nada complejo, el menú soporta 2 niveles, podemos escoger nuestro icono de font awesome le pones la ruta y que roles tendrán acceso a ese nivel, para acomodar el menú solo basta con arrastrar y soltar los items
Video Demostrativo

CURSO CREANDO UN SISTEMA DE PUNTO DE VENTA EN CODEIGNITER 4

Dejamos este tutorial en el cual veremos como hacer de la manera mas fácil un sistema de punto de venta similar al JCPOS

Lo que tendrá el sistema será lo siguiente

  1. Administrador de usuarios
  2. Catalogo de categorias
  3. Catalogo de productos
  4. Catalogo de clientes
  5. Cotizaciones
  6. Ventas
  7. Control de inventarios
  1. Introducción – Creando Proyecto #01
  2. Construyendo Menú #02
  3. Creando catalogo de empresas #03
  4. Creando modulo de configuración de correo electrónico #04
  5. Creando CRUD de categorías usando AutoCrud #05
  6. Creando CRUD de clientes #06
  7. Creando CRUD de productos #07
  8. Descargar catálogos del SAT CFDI en SQLITE
  9. Creando CRUD de sucursales #09
  10. Creando CRUD de Series Electrónicas #10
  11. Creando CRUD de Proveedores #11
  12. Cómo crear un módulo de kardex de inventario en CodeIgniter 4 #12
  13. Cómo crear un módulo de ventas con factura electrónica en CodeIgniter 4 #13
  14. Creando el CRUD de Tipos de Vehículos #14
  15. Creando CRUD de Vehículos #15
  16. Creando CRUD de choferes en CodeIgniter 4 #16
  17. Descarga Masiva en PHP CodeIgniter 4 #17
  18. Modulo para complemento de pago en CodeIgniter 4 PHP 8 #18
  19. Creando CRUD de ubicaciones para la carta porte 3.0 en CodeIgniter 4 #19
  20. Agregando campos nuevos para vehículos y usarlo en carta porte 3.0 en CodeIgniter 4 #20
  21. Agregando campos nuevos para choferes y usarlo en carta porte 3.0 en CodeIgniter 4 #21
  22. Creando CRUD de remolques para la carta porte 3.0 en CodeIgniter 4 #22

CI4JCPOS – INTRODUCCION #01

En esta serie de publicaciones vamos a hacer un sistema de punto de venta con el Framework de PHP CodeIgniter 4

El sistema será igual al JCPOS que ya veníamos haciendo pero con todas las ventajas que ofrece CodeIgniter

Primero crearemos el proyecto con Composer

composer create-project codeigniter4/appstarter ci4jcpos

Creamos nuestra base de datos en phpmyadmin con el nombre ci4jcpos

CREATE SCHEMA ci4jcpos DEFAULT CHARACTER SET utf8 COLLATE utf8_spanish2_ci

Ahora instalamos la plantilla boilerplate el cual nos instalara la plantilla adminlte3 con el administrador de usuario auth, lo instalamos corriendo el siguiente comando dentro del proyecto

En mi caso yo deje en así app/config/database.php

    public array $default = [
        'DSN'      => '',
        'hostname' => '127.0.0.1',
        'username' => 'root',
        'password' => '',
        'database' => 'ci4jcpos',
        'DBDriver' => 'MySQLi',
        'DBPrefix' => '',
        'pConnect' => false,
        'DBDebug'  => true,
        'charset'  => 'utf8',
        'DBCollat' => 'utf8_general_ci',
        'swapPre'  => '',
        'encrypt'  => false,
        'compress' => false,
        'strictOn' => false,
        'failover' => [],
        'port'     => 3306,
    ];
composer require julio101290/boilerplate

Ahora siguiente las instrucciones del paquete https://packagist.org/packages/julio101290/boilerplate corremos el comando migrate para 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

En app/config/auth.php modifcamos la matriz/arreglo de $views y la dejamos asi

       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',
    ];

En app/config/filters.php en la matriz aliases agregamos los siguientes valores, ojo, no quitar los que estaban, solo agregar los que hacen falta

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

Instalamos boilerplate con el siguiente comando

php spark boilerplate:install

En App/config/validation.php agregamos lo siguiente en la matriz de $$rulesSet

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

En App/entities/users.php en la matriz $cast lo dejamos así

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

En app/controller/home.php lo dejamos así para que entre directamente al administrador

<?php

namespace App\Controllers;

class Home extends BaseController
{
    public function index()
    {
        return redirect()->to("/admin");
    }
}

En app/config/constants.php al final del archivo le agregamos la siguiente constante para que nos permita debugear, una vez que este en producción se tiene que quitar


define("ENVIRONMENT","development");

En app/config/app.php le quita a la variable $indexPage el valor dejandolo en blanco, la variable defaultLocale y supportedLocales la dejamos con el valor “es”

public string $indexPage = '';
public string $defaultLocale = 'es';
public array $supportedLocales = ['en'];

En el archivo app/config/boilerplate.php asignamos el nombre de la app y en $i18n que es para el lenguaje de los datatables lo dejamos como Spanish

Y listo ya tenemos nuestro login y adminlte3 listo para agregarle los módulos
Video Demostrativo

CI 4.0 JCPOS Punto de Venta En CodeIgniter4 Introducción #1

JCPOS es un sistema de punto de venta (POS) de código abierto desarrollado en PHP. Recientemente, JCPOS se ha migrado a CodeIgniter 4, lo que ha mejorado significativamente su rendimiento y escalabilidad. En este artículo, exploraremos los detalles de la migración de JCPOS a CodeIgniter 4 y sus beneficios.

Antes de profundizar en la migración de JCPOS a CodeIgniter 4, es importante comprender qué es CodeIgniter y por qué es una opción popular para desarrolladores de PHP.

CodeIgniter es un framework PHP de código abierto que se utiliza para desarrollar aplicaciones web dinámicas y sitios web. Es popular debido a su facilidad de uso, velocidad y seguridad. Además, CodeIgniter tiene una gran comunidad de desarrolladores que proporciona soporte y actualizaciones regulares.

La migración de JCPOS a CodeIgniter 4 implica actualizar el código de JCPOS para que sea compatible con la última versión del framework. CodeIgniter 4 presenta varias mejoras y cambios significativos en comparación con su versión anterior, CodeIgniter 3. Algunas de estas mejoras incluyen:

  • Mayor rendimiento y velocidad gracias a la utilización de características avanzadas de PHP 7
  • Soporte mejorado para la creación de APIs RESTful
  • Mejoras en la seguridad y en la gestión de sesiones
  • Uso de clases y métodos modernos para mejorar la legibilidad y mantenibilidad del código
  • Flexibilidad y escalabilidad mejoradas gracias a su arquitectura modular

Para migrar JCPOS a CodeIgniter 4, los desarrolladores tuvieron que actualizar el código de JCPOS para utilizar las nuevas características y métodos de CodeIgniter 4. Esto incluyó cambios en la estructura del directorio, en la configuración del archivo y en el uso de nuevos métodos y características de CodeIgniter 4.

Una vez completada la migración, JCPOS obtuvo varios beneficios significativos. En primer lugar, el rendimiento y la velocidad de JCPOS se mejoraron considerablemente gracias a la utilización de características avanzadas de PHP 7 y a la optimización de la arquitectura de CodeIgniter 4. Esto significa que el sistema puede manejar más transacciones y usuarios simultáneamente, lo que es especialmente importante en entornos de punto de venta de alta demanda.

Además, la migración a CodeIgniter 4 proporcionó una mayor flexibilidad y escalabilidad a JCPOS. La arquitectura modular de CodeIgniter 4 permite que los desarrolladores de JCPOS agreguen fácilmente nuevas funcionalidades y características al sistema sin afectar negativamente al rendimiento o la estabilidad. Esto significa que JCPOS puede adaptarse y crecer para satisfacer las necesidades cambiantes de sus usuarios.

En conclusión, la migración de JCPOS a CodeIgniter 4 ha mejorado significativamente el rendimiento, la escalabilidad y la flexibilidad del sistema. Los desarrolladores de JCPOS han aprovechado las características avanzadas y la arquitectura modular de CodeIgniter 4 para optimizar el sistema y garantizar su compatibilidad con las últimas tecnologías y tendencias en el desarrollo web.

JCPOS Ultimate – Nuevo Repositorio #1

JCPOS: Una solución de punto de venta en constante evolución

En una reciente transmisión en vivo, el equipo de desarrollo detrás de JCPOS discutió los últimos cambios y mejoras en su solución de punto de venta. JCPOS es un sistema de punto de venta que ha estado en desarrollo desde el 2020, y ha evolucionado constantemente desde entonces para satisfacer las necesidades de sus usuarios.

Uno de los cambios más notables que el equipo ha realizado es el cambio de nombre de JC post a JCPOS. Esto se hizo para mantener la coherencia en el nombre a lo largo de los años, y evitar confusiones al cambiar el nombre cada año.

Otro de los cambios que se discutió en la transmisión fue la reciente publicación del repositorio de JCPOS en Github. Ahora está disponible para que cualquier persona interesada lo pueda descargar y explorar el código fuente. Además, se hizo hincapié en que los usuarios pueden contribuir al proyecto si encuentran algún problema o tienen alguna sugerencia.

El equipo también habló de algunos cambios específicos que han realizado en JCPOS, como la corrección de un problema en la bitácora que no estaba ordenando los botones de manera correcta. También se discutió la implementación de descuentos en las ventas, para asegurarse de que funcionen correctamente. Además, se está trabajando en la corrección de detalles menores, como la ordenación de la lista de ventas.

El equipo de JCPOS también habló sobre su intención de usar Composer para gestionar las dependencias del proyecto, lo que les permitirá mantenerse actualizados con las últimas versiones de las librerías que utilizan. También se mencionó la adición de impuestos a los productos, lo que permitirá la implementación de facturación electrónica en el futuro.

Finalmente, el equipo mencionó a sus patrocinadores y clientes, agradeciendo su apoyo y mencionando que los nombres de los patrocinadores se agregarán al repositorio en el futuro. También se proporcionó información sobre cómo patrocinar el proyecto.

En resumen, JCPOS es un proyecto de punto de venta en constante evolución. El equipo detrás de JCPOS está trabajando constantemente para mejorar la solución, escuchar las necesidades de los usuarios y mantenerse actualizado con las últimas tecnologías y herramientas de desarrollo. Si estás interesado en probar JCPOS o contribuir al proyecto, ¡asegúrate de visitar su repositorio en Github!

Cambios CI4 Boilerplate 1.3.0 Modal Draggable

Nuevo cambio en el cual se podrán mover cn el mouse los modales

Modal Draggable se refiere a la capacidad de arrastrar y mover un cuadro de diálogo modal (o ventana emergente) en una interfaz de usuario. Una ventana modal es un tipo de ventana emergente que requiere que el usuario realice una acción antes de poder interactuar con el resto de la aplicación o sitio web. A menudo se utilizan para solicitar información importante o para mostrar mensajes críticos al usuario.

La función Modal Draggable permite que el usuario arrastre la ventana modal a cualquier lugar en la pantalla para evitar que obstruya información importante o para hacer más fácil su uso en pantallas pequeñas. Esta función se implementa en algunos frameworks y librerías de JavaScript para la creación de interfaces de usuario, como Bootstrap, Materialize y jQuery UI.

Para activarlo en modales ya hechos solo basta con poner el siguiente código, ejemplo

 $(function () {
        $("#modal-edit-permission").draggable();
        $("#modal-create-permission").draggable();
    });

Código fuente
https://github.com/julio101290/boilerplate/

Desde Packist
https://packagist.org/packages/julio101290/boilerplate

Saludos y espero que les sirva

Nuevo Cambio boilerplate para CodeIgniter 4 Responsive Datatable

Se realizo un pequeño cambio en el proyecto boilerplate para CodeIgniter 4, en el cual las tablas podrán ser responsivas

Si ya están usando este proyecto solo hay que actualizar via composer

composer update

Y en la llamada del datatable agregar responsive:true por ejemplo

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

Y el resultado será el siguiente

Saludos y espero que les sea útil

Error CFDI 4.0 Nombre de receptor con tilde invertida `

Al momento de hacer la factura en su versión 4.0 hay clientes que en su razón social tienen la tilde invertida ` en la constancia de situación fiscal y aunque se copie tal cual no funciona y al timbrar la factura sale error de que no coincide el RFC con el nombre del receptor

La solucion que funciono para nosotros fue cambiar la tilde invertida por comilla simple ‘

Y con eso ya facturo correctamente

Página 13 de 139

Creado con WordPress & Tema de Anders Norén