Este tutorial/demo de código será el primero de una serie de tutoriales en los cuales estaremos realizando un CRUD utilizando Laravel para el BackEnd y React para el FrontEnd, para esto vamos a comenzar con un proyecto Laravel desde cero:
1. Crear proyecto de Laravel: Si quieres ver las opciones que tienes disponibles para crear un nuevo proyecto de Laravel puedes ver su documentación oficial, en mi caso yo lo haré con el instalador de Laravel con el comando (nuestro proyecto se llamará pos, de punto de venta):
laravel new posDespués de crear el proyecto debemos entrar en la carpeta que se acaba de crear para ejecutar algunas operaciones básicas para comenzar:
cd pos2. Generar los primeros modelos, migraciones y controladores: Para comenzar con el proyecto y hacerlo sencillo de seguir vamos a generar nada mas 3 Modelos: Impuesto, AmbitoImpuesto, CalculoImpuesto, estos modelos nos servirán mas adelante en nuestro sistema, son una pequeña parte de un todo, y vamos a generarlos con el comando:
php artisan make:model AmbitoImpuesto -a
php artisan make:model CalculoImpuesto -a
php artisan make:model Impuesto -aEl flag -a sirve para decirle a artisan que queremos que se generen las migraciones, factories y controladores para el modelo en cuestión.
3. Generar la autenticación para el proyecto:
php artisan make:auth4. Creando nuestras rutas: Cada uno de nuestros modelos será una tabla de catálogo para las cuales necesitamos un CRUD por lo cual necesitamos generar las rutas necesarias para manejar las peticiones:
Route::apiResource('ambitos-impuestos', 'AmbitoImpuestoController', [
'parameters' => [
'ambitos-impuestos' => 'model'
]
]);
Route::apiResource('calculos-impuestos', 'CalculoImpuestoController', [
'parameters' => [
'calculos-impuestos' => 'model'
]
]);
Route::apiResource('impuestos', 'ImpuestoController', [
'parameters' => [
'impuestos' => 'model'
]
]);5. Modificando las Migraciones generadas: Después de modificar las migraciones debemos ejecutarlas con el comando php artisan migrate y rellenar con algunos datos de prueba para que aparezcan en nuestras listas:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAmbitoImpuestosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ambitos_impuestos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('nombre');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('ambitos_impuestos');
}
}<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCalculoImpuestosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('calculos_impuestos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('nombre');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('calculos_impuestos');
}
}<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateImpuestosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('impuestos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('nombre');
$table->unsignedBigInteger('ambito_impuesto_id');
$table->unsignedBigInteger('calculo_impuesto_id');
$table->double('importe');
$table->string('etiqueta');
$table->timestamps();
$table->foreign('ambito_impuesto_id')->references('id')->on('ambitos_impuestos');
$table->foreign('calculo_impuesto_id')->references('id')->on('calculos_impuestos');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('impuestos');
}
}Ambito impuesto:
Cálculo impuesto:
6. Configuraciones y relaciones en los modelos:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AmbitoImpuesto extends Model
{
protected $table = 'ambitos_impuestos';
}<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CalculoImpuesto extends Model
{
protected $table = 'calculos_impuestos';
}7. El método index de los controladores:
<?php
namespace App\Http\Controllers;
use App\AmbitoImpuesto;
use Illuminate\Http\Request;
class AmbitoImpuestoController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$rs = AmbitoImpuesto::query()->get();
return response()->json([
'data' => $rs
], 200);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param \App\AmbitoImpuesto $ambitoImpuesto
* @return \Illuminate\Http\Response
*/
public function show(AmbitoImpuesto $ambitoImpuesto)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\AmbitoImpuesto $ambitoImpuesto
* @return \Illuminate\Http\Response
*/
public function edit(AmbitoImpuesto $ambitoImpuesto)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\AmbitoImpuesto $ambitoImpuesto
* @return \Illuminate\Http\Response
*/
public function update(Request $request, AmbitoImpuesto $ambitoImpuesto)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\AmbitoImpuesto $ambitoImpuesto
* @return \Illuminate\Http\Response
*/
public function destroy(AmbitoImpuesto $ambitoImpuesto)
{
//
}
}<?php
namespace App\Http\Controllers;
use App\CalculoImpuesto;
use Illuminate\Http\Request;
class CalculoImpuestoController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$rs = CalculoImpuesto::query()->get();
return response()->json([
'data' => $rs
], 200);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param \App\CalculoImpuesto $calculoImpuesto
* @return \Illuminate\Http\Response
*/
public function show(CalculoImpuesto $calculoImpuesto)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\CalculoImpuesto $calculoImpuesto
* @return \Illuminate\Http\Response
*/
public function edit(CalculoImpuesto $calculoImpuesto)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\CalculoImpuesto $calculoImpuesto
* @return \Illuminate\Http\Response
*/
public function update(Request $request, CalculoImpuesto $calculoImpuesto)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\CalculoImpuesto $calculoImpuesto
* @return \Illuminate\Http\Response
*/
public function destroy(CalculoImpuesto $calculoImpuesto)
{
//
}
}<?php
namespace App\Http\Controllers;
use App\Impuesto;
use Illuminate\Http\Request;
class ImpuestoController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$rs = Impuesto::query()
->with([
'ambito',
'calculo'
])
->get();
return response()->json([
'data' => $rs
], 200);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$data = $request->validate([
'nombre' => 'required|string',
'ambito_impuesto_id' => 'required|integer',
'calculo_impuesto_id' => 'required|integer',
'importe' => 'required|numeric',
'etiqueta' => 'required|string'
]);
$model = Impuesto::create($data);
return response()->json([
'data' => $model
], 200);
}
/**
* Display the specified resource.
*
* @param \App\Impuesto $impuesto
* @return \Illuminate\Http\Response
*/
public function show(Impuesto $impuesto)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Impuesto $impuesto
* @return \Illuminate\Http\Response
*/
public function edit(Impuesto $impuesto)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Impuesto $impuesto
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Impuesto $impuesto)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\Impuesto $impuesto
* @return \Illuminate\Http\Response
*/
public function destroy(Impuesto $impuesto)
{
//
}
}8. Preparar el proyecto para trabajar con React:
php artisan preset react9. Modificar la vista home.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-12" id="appimpuestos">
</div>
</div>
</div>
@endsection10. Modificar el archivo resources/js/app.js
/**
* First we will load all of this project's JavaScript dependencies which
* includes React and other helpers. It's a great starting point while
* building robust, powerful web applications using React + Laravel.
*/
require('./bootstrap');
/**
* Next, we will create a fresh React component instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
require('./components/Pos');11. El componente resources/js/componentes/Impuestos/AmbitoImpuestoList.js
import React, { useState, useEffect } from 'react'
export default function AmbitoImpuestoList({ handleChange, etiqueta, name }) {
const [list, setList] = useState([])
const getList = () => {
let url = '/ambitos-impuestos'
axios.get(url)
.then(resp => {
setList(resp.data.data)
})
.catch(err => {
console.log(err)
})
}
const handleChangeEvent = (e) => {
if (handleChange) {
handleChange(e)
} else {
console.info('no se ha definido una funcion manejadora del evento change')
}
}
useEffect(getList, [])
return (
<div className="form-group">
<label htmlFor="">{etiqueta ? etiqueta : 'Seleccione:'}</label>
<select onChange={handleChangeEvent} className="form-control" name={name ? name : ''}>
<option value={0}>Seleccione una opción de la lista</option>
{
list.map(item => <option key={item.id} value={item.id}>{item.nombre}</option>)
}
</select>
</div>
)
}12. El componente resources/js/componentes/Impuestos/CalculoImpuestoList.js
import React, { useState, useEffect } from 'react'
export default function CalculoImpuestoList({ handleChange, etiqueta, name }) {
const [list, setList] = useState([])
const getList = () => {
let url = '/calculos-impuestos'
axios.get(url)
.then(resp => {
setList(resp.data.data)
})
.catch(err => {
console.log(err)
})
}
const handleChangeEvent = (e) => {
if (handleChange) {
handleChange(e)
} else {
console.info('no se ha definido una funcion manejadora del evento change')
}
}
useEffect(getList, [])
return (
<div className="form-group">
<label htmlFor="">{etiqueta ? etiqueta : 'Seleccione:'}</label>
<select onChange={handleChangeEvent} className="form-control" name={name ? name : ''}>
<option value={0}>Seleccione una opción de la lista</option>
{
list.map(item => <option key={item.id} value={item.id}>{item.nombre}</option>)
}
</select>
</div>
)
}13. El componente resources/js/componentes/Impuestos/ImpuestosForm.js
import React, { useState } from 'react'
import AmbitoImpuestoList from './AmbitoImpuestoList'
import CalculoImpuestoList from './CalculoImpuestoList';
export default function ImpuestosForm({ titulo }) {
const [form, setForm] = useState({})
const handleSubmit = (e) => {
e.preventDefault()
const url = '/impuestos'
axios.post(url, form)
.then(resp => {
console.log('completado')
})
.catch(err => {
console.log(err)
})
}
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
})
}
return (
<div className="card">
<div className="card-header">
{titulo ? titulo : 'Formulario de impuestos'}
</div>
<div className="card-body">
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="">Nombre del impuesto</label>
<input onChange={handleChange} type="text" name="nombre" className="form-control" placeholder="Nombre del impuesto" />
</div>
<AmbitoImpuestoList
etiqueta="Ámbito del impuesto"
name="ambito_impuesto_id"
handleChange={handleChange}
/>
<CalculoImpuestoList
etiqueta="Cálculo del impuesto"
name="calculo_impuesto_id"
handleChange={handleChange}
/>
<div className="form-group">
<label htmlFor="">Importe</label>
<input onChange={handleChange} type="number" name="importe" className="form-control" placeholder="Importe" />
</div>
<div className="form-group">
<label htmlFor="">Etiqueta en facturas</label>
<input onChange={handleChange} type="text" name="etiqueta" className="form-control" placeholder="Etiqueta en facturas" />
</div>
<button type="submit" className="btn btn-primary">Guardar</button>
</form>
</div>
</div>
)
}14. El componente resources/js/componentes/Impuestos/Impuestos.js
import React from 'react'
import ImpuestosForm from './ImpuestosForm';
export default function Impuestos() {
return (
<ImpuestosForm />
)
}15. El componente resources/js/componentes/Pos.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Impuestos from './Impuestos/Impuestos';
export default class Pos extends Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-12">
<Impuestos />
</div>
</div>
</div>
);
}
}
if (document.getElementById('appimpuestos')) {
ReactDOM.render(<Pos />, document.getElementById('appimpuestos'));
}16. Compilar nuestros assets con el comando:
npm run dev17. Comprobar los resultados: El resultado en pantalla debería ser algo similar a esto:
Si rellenamos el formulario y presionamos el botón de guardar deberíamos poder ver un nuevo registro en la base de datos.


