WEB3DEV Español

Cover image for Construir una Aplicación de Rastreo Descentralizada de una Cadena de Suministro con Contratos Inteligentes
Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Construir una Aplicación de Rastreo Descentralizada de una Cadena de Suministro con Contratos Inteligentes

Image description

En el escenario empresarial actual que evoluciona rápidamente, las cadenas de suministro juegan un rol crucial en asegurar el movimiento eficiente de los bienes y servicios en el mundo. Sin embargo, la complejidad inherente y la participación de múltiples stakeholders en la cadena de suministros lleva a tener retos en mantener la transparencia, rastreabilidad y responsabilidad. Aquí es donde entra la tecnología blockchain y los contratos inteligentes, una solución revolucionaria que tiene el potencial de transformar la administración de las cadenas de suministro. En este artículo, profundizaremos en la creación de una aplicación de rastreo descentralizada de una cadena de suministro usando contratos inteligentes, solventando el problema de la opacidad e ineficiencia de las cadenas de suministro.

El Problema: Transparencia y Rastreabilidad en las Cadenas de Suministro

La administración de cadenas de suministro tradicionales tienen varios problemas críticos. La falta de transferencia en las diferentes etapas de la cadena de suministro puede llevar a productos falsificados, retrasos e incluso a violaciones de los derechos humanos. Rastrear el recorrido de un objeto desde el manufacturador hasta el consumidor es una tarea pesada, que usualmente depende de intermediarios, los cuales, sus intereses puede que no sea asegurar la transparencia.

Más aún, la naturaleza de las bases de datos de la cadena de suministro hace que sean susceptibles a la manipulación de datos y al acceso no-autorizado. La coordinación y comunicación ineficiente en la participación de la cadena de suministro puede llevar a retrasos, incremento de los costos y reducción de la satisfacción del consumidor.

La Solución: El Rastreo Descentralizado de las Cadenas de Suministro con Contratos Inteligentes

La tecnología blockchain, con sus características inherentes de la descentralización, transparencia e inmutabilidad, presenta una prometedora solución a los retos que se encuentran las cadenas de suministro tradicionales. Los contratos inteligentes, que son códigos desplegados en la blockchain auto-ejecutables, proveen una forma segura y automatizada de hacer cumplir los acuerdos.

Implementando una aplicación de rastreo descentralizada de una cadena de suministro usando contratos inteligentes, podemos establecer un libro de contabilidad digital a prueba de manipulaciones que registra cada etapa del recorrido de un objeto. Este libro de contabilidad es accesible para todas las entidades autorizadas, asegurando la transparencia y rastreabilidad mientras se reduce la necesidad de un intermediario.

Tamaño del Proyecto

Nuestro proyecto apunta a desarrollar un sistema de rastreo de cadena de suministro que permita a los usuarios pedir, cancelar y rastrear objetos usando una aplicación descentralizada. Esto conlleva crear un contrato inteligente que administre todo el proceso y el frontend que interactúa con el contrato. El contrato inteligente incluirá funciones para pedir objetos, cancelar órdenes, actualizar el estado de los objetos y retirar la información del estado.

Beneficios de la Solución

La propuesta de solución ofrece multitudes de beneficios:

  1. Transparencia y Rastreabilidad: Con los datos registrados en una blockchain inmutable, todos los stakeholders pueden rastrear el recorrido de un objeto en tiempo real, asegurando transparencia y asegurando la confianza.
  2. Eficiencia: La automatización a través de los contratos inteligentes facilita los procesos, reduce los retrasos y elimina el papeleo, lo cual conlleva a tener eficiencia en toda la cadena de suministro.
  3. Costos Reducidos: Al eliminar los intermediarios y reducir las intervenciones manuales, se minimizan los costos asociados al papeleo, reconciliación y resolución de disputas.
  4. Seguridad de Datos: La naturaleza descentralizada de la solución hace que sea resistente a la filtración de datos y accesos sin autorizar, mejorando la seguridad de información sensible de la cadena de suministro.
  5. Confianza y Responsabilidad: la naturaleza anti-manipulación de la blockchain, asegura que la información registrada sea confiable, haciendo que la confianza aumente en los participantes de la cadena de suministro.
  6. Auditoría y Cumplimiento: La transparencia y naturaleza auditable de la blockchain, simplifica el cumplimiento de las regulaciones y los requisitos de auditoría.

Construir el Contrato Inteligente

Empecemos escribiendo el contrato inteligente usando Solidity, el lenguaje de programación de los contratos inteligentes de Ethereum:

//contracts/SupplyChain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract SupplyChain is Ownable {
enum Status {
Ordered,
Shipped,
Delivered,
Cancelled
}

struct Item {
uint id;
string name;
Status status;
address orderedBy;
address approvedBy;
address deliveredTo;
}

mapping(uint => Item) private items;
uint private itemCount;

function orderItem(string memory _name) public {
Item memory newItem = Item({
id: itemCount,
name: _name,
status: Status.Ordered,
orderedBy: msg.sender,
approvedBy: address(0),
deliveredTo: address(0)
});
items[itemCount] = newItem;
itemCount++;
}

function cancelItem(uint _id) public {
require(
items[_id].orderedBy == msg.sender,
"Only the person who ordered the item can cancel it"
);
require(
items[_id].status == Status.Ordered,
"Item can only be cancelled if it is in the Ordered state"
);
items[_id].status = Status.Cancelled;
}

function approveItem(uint _id) public onlyOwner {
require(
items[_id].status == Status.Ordered,
"Item must be in Ordered state to be approved"
);
items[_id].status = Status.Shipped;
items[_id].approvedBy = msg.sender;
}

function shipItem(uint _id) public onlyOwner {
require(
items[_id].status == Status.Shipped,
"Item must be in Shipped state to be shipped"
);
items[_id].status = Status.Delivered;
items[_id].deliveredTo = items[_id].orderedBy;
}

function getItemStatus(uint _id) public view returns (Status) {
return items[_id].status;
}

function getItem(uint _id) public view returns (Item memory) {
return items[_id];
}

function getItemCount() public view returns (uint) {
return itemCount;
}
}
Enter fullscreen mode Exit fullscreen mode

Despliegue

Despliega el código del Contrato Inteligente:

//scripts/deploy.ts
import { ethers } from "hardhat";

async function main() {
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
const unlockTime = currentTimestampInSeconds + 60;

const lockedAmount = ethers.utils.parseEther("0.001");

const Lock = await ethers.getContractFactory("SupplyChain");
const lock = await Lock.deploy();

await lock.deployed();

console.log(
`Lock with ${ethers.utils.formatEther(lockedAmount)}ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`
);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Escribir Pruebas con ethers.js

Para asegurar que nuestro contrato inteligente esté correcto, necesitamos escribir pruebas. Usaremos ethers.js, una biblioteca de JavaScript que interactúa con Ethereum:

//test/supplyChain.ts
import { ethers } from "hardhat";
import { Contract } from "ethers";
import { expect } from "chai";

describe("SupplyChain", function () {
let supplyChain: Contract;
let owner: any;
let addr1: any;
let addr2: any;

beforeEach(async function () {
const SupplyChain = await ethers.getContractFactory("SupplyChain");
[owner, addr1, addr2] = await ethers.getSigners();
supplyChain = await SupplyChain.deploy();
await supplyChain.deployed();
});

it("Should create an item and retrieve its status", async function () {
await supplyChain.orderItem("Item 1");
const status = await supplyChain.getItemStatus(0);
expect(status).to.equal(0);
});

it("Should cancel an item if it is in Ordered state", async function () {
await supplyChain.orderItem("Item 1");
await supplyChain.cancelItem(0);
const status = await supplyChain.getItemStatus(0);
expect(status).to.equal(3);
});

it("Should not allow non-owners to approve an item", async function () {
await supplyChain.orderItem("Item 1");
await expect(supplyChain.connect(addr1).approveItem(0)).to.be.revertedWith("Ownable: caller is not the owner");
});

it("Should approve an item if it is in Ordered state", async function () {
await supplyChain.orderItem("Item 1");
await supplyChain.approveItem(0);
const item = await supplyChain.getItem(0);
expect(item.status).to.equal(1);
expect(item.approvedBy).to.equal(owner.address);
});

it("Should not allow non-owners to ship an item", async function () {
await supplyChain.orderItem("Item 1");
await supplyChain.approveItem(0);
await expect(supplyChain.connect(addr1).shipItem(0)).to.be.revertedWith("Ownable: caller is not the owner");
});

it("Should ship an item if it is in Shipped state", async function () {
await supplyChain.orderItem("Item 1");
await supplyChain.approveItem(0);
await supplyChain.shipItem(0);
const item = await supplyChain.getItem(0);
expect(item.status).to.equal(2);
expect(item.deliveredTo).to.equal(owner.address);
});
});
Enter fullscreen mode Exit fullscreen mode

Fragmento del Código Frontend

import { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import detectEthereumProvider from '@metamask/detect-provider';

//ln -s ../../smart-contract/artifacts/contracts/SupplyChain.sol/SupplyChain.json node_modules/SupplyChain.json
import SupplyChainABI from "SupplyChain.json";

import InputField from './InputField';
import Button from './button';

// Cambia la dirección del contrato inteligente para que coincida con la dirección de tu contrato desplegado
const contractAddress = "0x0B8693d0eD71BBd841ECE42BC8A36141737A4F4b";

function SupplyChain() {
const [itemName, setItemName] = useState('');
const [itemId, setItemId] = useState(0);
const [itemDetails, setItemDetails] = useState<any>(null);
const [items, setItems] = useState<any>([]);
const [loading, setLoading] = useState(false);

useEffect(() => {
const initializeMetaMask = async () => {
const provider: any = await detectEthereumProvider();
if (provider) {
await provider.request({ method: 'eth_requestAccounts' });

const ethersProvider = new ethers.providers.Web3Provider(provider);
const signer = ethersProvider.getSigner();
const supplyChainContract = new ethers.Contract(contractAddress, SupplyChainABI.abi, signer);
// Actualiza la instancia del contrato con el nuevo firmante
setSupplyChainContract(supplyChainContract);
} else {
console.error('MetaMask not found');
}
};

initializeMetaMask();
}, []);

const [supplyChainContract, setSupplyChainContract] = useState<any>(null);

useEffect(() => {
if (supplyChainContract) {
loadItems();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [supplyChainContract]);

const loadItems = async () => {
try {
setLoading(true);
const count = await supplyChainContract.getItemCount();
const itemsArray: any = [];
for (let i = 0; i < count.toNumber(); i++) {
const item = await supplyChainContract.getItem(i);
itemsArray.push(item);
}
setItems(itemsArray);
setLoading(false);
setItemName("")
} catch (error) {
console.error('Error loading items:', error);
setLoading(false);
}
};

const orderItem = async () => {
try {
const tx = await supplyChainContract.orderItem(itemName);
await tx.wait();
console.log('Item ordered successfully!');
loadItems();
} catch (error) {
console.error('Error ordering item:', error);
}
};

const cancelItem = async (id: any) => {
try {
const tx = await supplyChainContract.cancelItem(id);
await tx.wait();
console.log('Item cancelled successfully!');
loadItems();
} catch (error) {
console.error('Error cancelling item:', error);
}
};

const approveItem = async (id: any) => {
try {
const tx = await supplyChainContract.approveItem(id);
await tx.wait();

console.log('Item approved successfully!');
loadItems();
} catch (error) {
console.error('Error approving item:', error);
}
};

const shipItem = async (id: any) => {
try {
const tx = await supplyChainContract.shipItem(id);
await tx.wait();

console.log('Item shipped successfully!');
loadItems();
} catch (error) {
console.error('Error shipping item:', error);
}
};

const getItem = async () => {
try {
setLoading(true);

const item = await supplyChainContract.getItem(itemId);
console.log('Item:', item);
setItemDetails(item);
setLoading(false);
} catch (error) {
console.error('Error getting item:', error);
setLoading(false);
}
};

function getStatusText(status: number): string {
switch (status) {
case 0:
return "Ordered";
case 1:
return "Approved";
case 2:
return "Delivered";
case 3:
return "Cancelled";
default:
return "";
}
}

function displayPartialAddress(address: string) {
if (address.length <= 7) {
return address;
} else {
const firstThree = address.substring(0, 3);
const lastFour = address.substring(address.length - 4);
return `${firstThree}...${lastFour}`;
}
}

const cols = [
"ID", "Name", "Status", "Ordered by", "Approved by", "Delivered to"
];

return (
<>
<div className="overflow-hidden rounded-lg border border-gray-200 shadow-md m-5" data-aos="fade-up" data-aos-offset="300" data-aos-easing="ease-in-sine">
<div className="m-10 flex justify-between space-x-5">
<div className="w-full md:w-1/2 flex justify-between items-center space-x-3">
<InputField
value={itemName}
onchange={(e: string) => setItemName(e)}
placeholder="Type your item here ..."
/>
<Button
title="Order"
onClick={orderItem}
disabled={itemName === ""}
/>
</div>
<div className="w-full md:w-1/2 flex justify-between items-center space-x-3">
<InputField
value={itemId}
type={"number"}
onchange={(e: any) => setItemId(e)}
placeholder="Enter item ID ..."
/>
<Button
title="Search"
onClick={getItem}
disabled={itemId < 0}
/>

<button className='mx-5 text-blue-600' onClick={loadItems}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" className="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>
</button>
</div>
</div>
<div className='m-10'>
<div className="mb-4">
{loading ? (
<div className="text-center text-green-600">Loading...</div>
) : itemDetails && (
<div className="border border-gray-300 p-4 rounded">
<div>Item ID: {`${itemDetails.id}`}</div>
<div className='text-sm'>Name: {itemDetails.name}</div>
<div className='text-sm'>Status: {getStatusText(itemDetails.status)}</div>
<div className='text-sm'>Ordered By: {itemDetails.orderedBy}</div>
<div className='text-sm'>Approved By: {itemDetails.approvedBy}</div>
<div className='text-sm'>Delivered To: {itemDetails.deliveredTo}</div>
</div>
)}
</div>
</div>
<table className="w-full border-collapse bg-white text-left text-sm text-gray-500">
<thead className="bg-gray-50">
<tr>
{cols.map((col) => (
<th
scope="col"
className="px-6 py-4 font-medium text-gray-900"
key={col}
>
{col}
</th>
))}
<th
scope="col"
className="px-6 py-4 text-center font-medium text-gray-900"
>
Actions
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100 border-t border-gray-100">
{items.length < 1 ? (
<tr>
<td
colSpan={cols.length + 1}
className="text-center text-xl py-10"
>
No items to display
</td>
</tr>
) :
(
items
.sort((a: any, b: any) => b.id - a.id)
.map((item: any, index: number) => (
<tr className="hover:bg-gray-50" key={index}>
<th className="flex gap-3 px-6 py-2 font-normal text-gree-900">
{`${item.id}`}
</th>
<td
className="px-6 py-2 cursor-pointer"
onClick={() => setItemDetails(item)}
>
{item.name}
</td>
<td className="px-6 py-2">
<span
className={`inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-1 text-xs font-semibold ${item.status === 3
? 'text-red-600'
: 'text-green-600'
}`}
>
<span
className={`h-1.5 w-1.5 rounded-full ${item.status === 3
? 'bg-red-600'
: 'bg-green-600'
}`}
/>
{getStatusText(item.status)}
</span>
</td>
<td className="px-6 py-2">
{displayPartialAddress(item.orderedBy)}
</td>
<td className="px-6 py-2">
{displayPartialAddress(item.approvedBy)}
</td>
<td className="px-6 py-2">
{displayPartialAddress(item.deliveredTo)}
</td>
<td className="px-6 py-2 text-center">
<div className="flex justify-end space-x-3 gap-4">
{item.status === 0 && (
<>

<button className='text-red-600' onClick={() => cancelItem(item.id)}>Cancel</button>
<button className='text-green-600' onClick={() => approveItem(item.id)}>Approve</button>
</>
)}
{item.status === 1 && (
<button className='text-blue-600' onClick={() => shipItem(item.id)}>Ship Item</button>
)}
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</>
);
}

export default SupplyChain;
Enter fullscreen mode Exit fullscreen mode

La Implementación Completa

La implementación completa de la aplicación de rastreo descentralizado de la cadena de suministro, junto con el contrato inteligente y el frontend, puede encontrarse en GitHub. Este repositorio contiene el código del contrato inteligente Solidity, el código frontend de JavaScript usando Ethers.js e instrucciones detalladas de la configuración.

Al explorar este repositorio, puedes ganar un entendimiento profundo de cómo construir y desplegar una aplicación de rastreo descentralizado de la cadena de suministro que aprovecha el poder de los contratos inteligentes y de la tecnología blockchain.

Conclusión

El potencial de la blockchain y contratos inteligentes para revolucionar la administración de las cadenas de suministro es innegable. Al desarrollar una aplicación de rastreo descentralizada de cadena de suministro, solventamos los retos de transparencia y rastreabilidad, ofreciendo una solución que mejora la eficiencia, reduce los costos y construye confianza entre todos los stakeholders. Mientras abrazamos este futuro impulsado por la tecnología, la forma que administramos y optimizamos las cadenas de suplemento está destinada a sufrir un cambio transformador que beneficia tanto a las empresas como a los consumidores.

Este artículo es una traducción de Bizenforce, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)