Cómo una Escuela Lleva Control de 500 Netbooks Sin Perder Ninguna (Gracias a Blockchain)

"Solidity + OpenZeppelin RBAC + Wagmi v2 + Next.js + Foundry: La combinación tecnológica que hizo posible un sistema de trazabilidad donde 500 netbooks educativas son rastreadas sin punto único de fallo."

En el sector educativo público, la pérdida o extravió de dispositivos es un problema crónico. Una escuela recibe 500 netbooks y cada una necesita ser asignada a un estudiante, auditada periódicamente y rastreada durante todo su ciclo de vida. ¿Qué pasa cuando la base de datos central se corrompe, se pierde o es manipulada? Blockchain resuelve esto haciendo que cada evento sea inmutable y auditado públicamente.


SupplyChainTracker traceability system

Subtítulo técnico: Trazabilidad Web3 full-stack en EVM con RBAC en Solidity e integración Wagmi v2


🎯 Rastrear Dispositivos Educativos para que Nadie los Pierda de Vista

La situación: Una escuela recibe 500 netbooks. Cada una necesita ser asignada a un estudiante, auditada regularmente y rastreada durante todo su ciclo de vida. ¿Qué pasa cuando la base de datos central se corrompe, se pierde o es manipulada? Blockchain resuelve esto haciendo que cada evento sea inmutable y públicamente auditable.


📊 El Problema: Cuando las Bases de Datos Centralizadas Fallan

En la gestión tradicional de cadena de suministro, una sola entidad controla la base de datos. Esto crea varios problemas críticos:

Problema Impacto
Punto único de fallo Si la base de datos se corrompe, se pierden todos los datos de rastreo
Falta de transparencia Estudiantes, padres y auditores no pueden verificar los datos independientemente
Manipulación interna Los admins pueden modificar registros sin que nadie lo sepa
Sin historial de auditoría Los cambios históricos están ocultos o son fácilmente alterables

Para instituciones educativas, esto significa cientos de dispositivos cuyo ciclo de vida — desde el registro hasta la asignación estudiantil hasta el mantenimiento — no puede ser rastreado de forma confiable.

flowchart LR
    A[Base de Datos Centralizada] --> B[Admin Puede Modificar]
    A --> C[No Auditoría Pública]
    A --> D[Punto Único de Fallo]
    
    style A fill:#ffcccc,stroke:#ff0000
    style B fill:#ffcccc,stroke:#ff0000
    style C fill:#ffcccc,stroke:#ff0000
    style D fill:#ffcccc,stroke:#ff0000

💡 La Solución: Blockchain como Verdad Compartida

SupplyChainTracker registra cada evento del ciclo de vida de cada dispositivo inmutablemente en la blockchain. Una vez escrito, nadie — ni siquiera el admin del sistema — puede alterar o borrar un registro.

Cómo Funciona Conceptualmente

sequenceDiagram
    participant School as 🏫 Admin Escolar
    participant BC as ⛓️ Blockchain
    participant Student as 👨‍🎓 Estudiante
    participant Auditor as 🔍 Auditor
    
    School->>BC: Registrar Lote de Netbooks
    BC-->>School: Transacción Confirmada
    School->>BC: Asignar a Estudiante
    BC-->>Student: Propiedad Registrada
    Auditor->>BC: Solicitar Historial de Auditoría
    BC-->>Auditor: Log Inmutable Completo
    Auditor->>BC: Registrar Auditoría de Hardware
    BC-->>Auditor: Auditoría Confirmada

La idea clave: la blockchain es la única fuente de verdad que todos confían porque nadie puede manipularla sola.


🛠️ Tecnologías Usadas

Capa Tecnología Propósito
Smart Contracts Solidity ^0.8.24, OpenZeppelin Lógica de trazabilidad + RBAC on-chain
Testing Foundry (Forge + Anvil) Tests unitarios rápidos y despliegue
Frontend React 19, Next.js 15+, TypeScript Interfaz web operativa completa
Web3 Wagmi v2, Viem, Ethers.js Hooks type-safe para interacción EVM
Estilos TailwindCSS UI responsive y accesible
State Management TanStack Query Caché de estado on-chain

🔧 Implementación: Arquitectura del Smart Contract

El Contrato Central

El sistema implementa un contrato inteligente central (SupplyChainTracker) que gestiona toda la lógica de negocio:

// Operaciones clave del contrato assignToStudent(serial, schoolHash, studentHash) auditHardware(serial, passed, reportHash) validateSoftware(serial, version, passed) registerNetbooks(serials, batches, modelSpecs)

Ejemplo: Roles RBAC en OpenZeppelin

El control de acceso usa RBAC (Role-Based Access Control) nativo de OpenZeppelin:

// contracts/SupplyChainTracker.sol - Definición de roles import "@openzeppelin/contracts/access/AccessControl.sol"; contract SupplyChainTracker is AccessControl { // Roles definidos bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant AUDITOR_ROLE = keccak256("AUDITOR_ROLE"); bytes32 public constant SCHOOL_ROLE = keccak256("SCHOOL_ROLE"); bytes32 public constant TECH_ROLE = keccak256("TECH_ROLE"); // Mapeo de roles a permisos | Role | Permisos | Quién lo usa | |------|---------|-------------| | `ADMIN_ROLE` | Registrar dispositivos, asignar a estudiantes | Admin escolar | | `AUDITOR_ROLE` | Registrar auditorías de hardware/software | Inspector técnico | | `SCHOOL_ROLE` | Solicitar estado de dispositivos | Representante escolar | | `TECH_ROLE` | Validar software | Técnico IT | }

Ejemplo: Asignación de Roles RBAC

// contracts/SupplyChainTracker.sol - Asignación de roles function grantRoleWithCheck(bytes32 role, address account) external onlyRole(DEFAULT_ADMIN_ROLE) { // Verificar que la cuenta no tiene ya el rol if (!hasRole(role, account)) { grantRole(role, account); // Emitir evento para que el frontend actualice la UI emit RoleGranted(role, account, msg.sender, block.timestamp); } } // Ejemplo de cómo se asignan roles en la práctica: // Admin asigna rol SCHOOL a un representante de escuela adminTx = await contract.grantRoleWithCheck(SCHOOL_ROLE, schoolRepresentativeAddress); // Admin asigna rol AUDITOR a un inspector adminTx = await contract.grantRoleWithCheck(AUDITOR_ROLE, inspectorAddress);

Ejemplo Completo: Función assignToStudent

// contracts/SupplyChainTracker.sol - Asignación completa a estudiante struct NetbookAssignment { string serial; // Número de serie único address assignedTo; // Dirección del estudiante uint256 assignedAt; // Timestamp de asignación string schoolHash; // Hash de la escuela (privacidad) string studentHash; // Hash del estudiante (privacidad) bool active; // ¿Está actualmente asignada? } mapping(string => NetbookAssignment) public assignments; mapping(string => uint256) public lastAudit; function assignToStudent( string calldata serial, string calldata schoolHash, string calldata studentHash ) external onlyRole(ADMIN_ROLE) { // Verificar que la netbook existe y está disponible require(netbooks[serial], "Netbook no registrada"); require(!assignments[serial].active, "Ya asignada"); // Registrar la asignación assignments[serial] = NetbookAssignment({ serial: serial, assignedTo: _hashToAddress(studentHash), assignedAt: block.timestamp, schoolHash: schoolHash, studentHash: studentHash, active: true }); // Emitir evento para el frontend emit NetbookAssigned(serial, assignments[serial].assignedTo, schoolHash, block.timestamp); } // Función para devolver una netbook (cuando el estudiante se gradúa) function returnNetbook(string calldata serial) external onlyRole(ADMIN_ROLE) { require(assignments[serial].active, "No asignada"); assignments[serial].active = false; emit NetbookReturned(serial, assignments[serial].assignedTo, block.timestamp); }

Control de Acceso: RBAC On-Chain

graph TB
    subgraph Roles
        ADMIN[Admin Role]
        AUDITOR[Auditor Role]
        SCHOOL[School Role]
        TECH[Tech Role]
    end
    
    subgraph Acciones
        REG[Registrar Netbooks]
        ASN[Asignar a Estudiante]
        AUD[Auditoría Hardware]
        VAL[Validación Software]
    end
    
    ADMIN --> REG
    ADMIN --> ASN
    AUDITOR --> AUD
    TECH --> VAL
    SCHOOL -->|Consulta| BC[Estado Blockchain]
    
    style ADMIN fill:#00f2ff,stroke:#00f2ff,color:#000
    style AUDITOR fill:#ff00f2,stroke:#ff00f2,color:#fff
    style SCHOOL fill:#00ff88,stroke:#00ff88,color:#000
    style TECH fill:#ffaa00,stroke:#ffaa00,color:#000

Archivos Clave del Repositorio


💻 Frontend: UX Web3 con Next.js

La aplicación web cubre todo el flujo operativo:

  • Gestión de dispositivos: Registro, asignación a estudiantes, auditoría de hardware y software
  • Gestión de roles: Solicitud, aprobación y revocación de roles on-chain
  • Dashboard de auditorías: Historial inmutable de todos los eventos
  • Diagnóstico de servicios: Panel de salud de la conexión con blockchain

Ejemplo: Hook Wagmi para Consulta de Netbooks

// frontend/src/hooks/useNetbook.ts import { useReadContract, useWalletClient } from 'wagmi'; import { supplyChainABI } from '@/config/abi'; import { CONTRACT_ADDRESS } from '@/config'; export function useNetbook(serial: string) { // Leer estado on-chain de una netbook específica const { data: assignment, isLoading } = useReadContract({ address: CONTRACT_ADDRESS, abi: supplyChainABI, functionName: 'assignments', args: [serial], }); // Verificar roles del usuario conectado const { data: walletClient } = useWalletClient(); const hasAdminRole = useReadContract({ address: CONTRACT_ADDRESS, abi: supplyChainABI, functionName: 'hasRole', args: [ keccak256('ADMIN_ROLE'), walletClient?.account.address as `0x${string}`, ], }); return { assignment, isLoading, hasAdminRole, serial, }; } // Uso en componente React function NetbookDetail({ serial }: { serial: string }) { const { assignment, isLoading } = useNetbook(serial); if (isLoading) return <Spinner />; return ( <div className="card"> <h3>Netbook #{serial}</h3> <p>Asignada a: {assignment?.studentHash}</p> <p>Estado: {assignment?.active ? '🟢 Activa' : '🔴 Devuelta'}</p> <p>Asignada en: {new Date(Number(assignment?.assignedAt) * 1000).toLocaleDateString()}</p> </div> ); }

Ejemplo: Test con Foundry

// test/SupplyChainTracker.t.sol pragma solidity ^0.8.24; import "forge-std/Test.sol"; import "../contracts/SupplyChainTracker.sol"; contract SupplyChainTrackerTest is Test { SupplyChainTracker public contract; address public admin; address public auditor; address public school; address public tech; function setUp() public { contract = new SupplyChainTracker(); admin = address(0x1); auditor = address(0x2); school = address(0x3); tech = address(0x4); // Asignar roles vm.prank(admin); contract.grantRole(contract.ADMIN_ROLE(), admin); vm.prank(admin); contract.grantRole(contract.AUDITOR_ROLE(), auditor); vm.prank(admin); contract.grantRole(contract.SCHOOL_ROLE(), school); vm.prank(admin); contract.grantRole(contract.TECH_ROLE(), tech); } function testRegisterAndAssign() public { string[] memory serials = new string[](3); serials[0] = "NB-001"; serials[1] = "NB-002"; serials[2] = "NB-003"; string[] memory batches = new string[](3); batches[0] = "BATCH-2024-A"; batches[1] = "BATCH-2024-A"; batches[2] = "BATCH-2024-B"; vm.prank(admin); contract.registerNetbooks(serials, batches, "Model-X"); assertTrue(contract.netbooks("NB-001")); assertTrue(contract.netbooks("NB-002")); assertTrue(contract.netbooks("NB-003")); // Asignar a estudiante vm.prank(admin); contract.assignToStudent("NB-001", "school-hash", "student-hash"); (, address assignedTo, uint256 assignedAt, , , bool active) = contract.assignments("NB-001"); assertEq(active, true); assertGt(assignedAt, 0); } function testAuditRequiresAuditorRole() public { // Registrar netbook string[] memory serials = new string[](1); serials[0] = "NB-001"; vm.prank(admin); contract.registerNetbooks(serials, new string[](1), "Model-X"); // Intentar auditoría sin rol — debe fallar vm.prank(school); // school no tiene AUDITOR_ROLE vm.expectRevert(); contract.auditHardware("NB-001", true, "audit-hash"); // Auditoría con rol — debe funcionar vm.prank(auditor); contract.auditHardware("NB-001", true, "audit-hash"); assertEq(contract.lastAudit("NB-001"), block.timestamp); } }

📊 Comparación: Tradicional vs Blockchain

Aspecto Tradicional (Base Centralizada) Blockchain (SupplyChainTracker)
Punto de fallo Único (la base de datos) Distribuido (no hay pérdida)
Transparencia Solo admins ven datos Cualquiera puede auditar
Manipulación Admin puede alterar registros Inmutable, no se puede alterar
Verificación Depende de auditoría interna Pública, on-chain, instantánea
Historial Puede ser borrado Permanente, inmutable
Confianza En la institución En criptografía
Acceso Limitado a la organización Global, 24/7
Costo de auditoría Horas de trabajo manual Segundos, consulta on-chain

📈 Impacto: Por Qué la Blockchain Importa Aquí

Para la Escuela

  • Adiós a los registros perdidos: Cada dispositivo es rastreado on-chain
  • Auditorías transparentes: Padres y autoridades pueden verificar los registros
  • Responsabilidad: Cada acción es atribuida a un rol específico

Para los Estudiantes

  • Prueba de propiedad: Su dispositivo asignado está registrado en blockchain
  • Rastreo de transferencia: Cuando un estudiante se gradúa, el historial del dispositivo sigue

Para los Auditores

  • Historial inmutable: Nadie puede modificar retroactivamente los resultados de auditoría
  • Verificación pública: Cualquiera puede verificar el estado de cualquier dispositivo

🤔 Por Qué Esto Importa Más Allá de la Educación

El mismo patrón de trazabilidad se aplica a:

  • Rastreo de equipos médicos en redes hospitalarias
  • Autenticación de productos de lujo (demostrar que un producto es genuino)
  • Cadenas de seguridad alimentaria (rastreo de la granja a la mesa)
  • Gestión de activos gubernamentales (inventario de equipamiento público)

La blockchain no es solo para aplicaciones financieras — cualquier industria que necesite registros a prueba de manipulaciones puede beneficiarse de esta arquitectura.


✅ Lecciones Aprendidas

Este proyecto fue el primer contacto serio con desarrollo full-stack Web3. Los desafíos más interesantes fueron:

  1. Sincronización de estado: Los datos on-chain son asíncronos y la UI necesita patrones específicos para evitar estados inconsistentes
  2. Control de acceso on-chain: Implementar RBAC directamente en el contrato es más gas-efficient que off-chain
  3. Testing con Foundry: La velocidad de forge test comparado con Hardhat es notable — los tests corren en segundos, no minutos


🔗 Explorar el Código

Código fuente completo: github.com/87maxi/SupplyChainTracker2

Próximo paso: Cloná el repo, ejecutá anvil para una blockchain local, e interactuá con el contrato usando el frontend Next.js. No se requiere wallet para modo de desarrollo.


Proyecto del Master en Blockchain y Web3 — CodeCrypto Academy

💬

Comentarios

Powered by Giscus · GitHub Discussions

🧠 Web3 & Blockchain