Skip to main content

System Architecture

Huellitas Pet Shop follows a modern three-tier architecture that separates concerns between presentation, business logic, and data persistence. This design ensures scalability, maintainability, and testability.

Architecture Overview

Frontend Architecture (React SPA)

The frontend is a Single Page Application built with React that provides a dynamic, responsive user interface.

Technology Stack

React 18

Modern UI library with hooks, context, and functional components

React Router Dom

Client-side routing for seamless navigation

Axios

Promise-based HTTP client for API communication

Vite

Lightning-fast build tool and dev server

Component Structure

huellitas-frontend/
├── src/
│   ├── App.jsx                 # Root component with routing
│   ├── main.jsx               # Application entry point
│   ├── components/            # Reusable UI components
│   │   ├── Header.jsx
│   │   ├── Footer.jsx
│   │   ├── FilterSidebar.jsx
│   │   ├── Producto/
│   │   │   └── ProductCard.jsx
│   │   ├── Carrito/
│   │   │   ├── CarritoItem.jsx
│   │   │   ├── CarritoResumen.jsx
│   │   │   └── CarritoVacio.jsx
│   │   ├── Login/
│   │   │   ├── LoginForm.jsx
│   │   │   ├── RegisterForm.jsx
│   │   │   └── AuthUI.jsx
│   │   └── Contacto/
│   │       ├── ContactoForm.jsx
│   │       └── ContactoInfo.jsx
│   ├── pages/                 # Page components
│   │   ├── Home/
│   │   │   └── Inicio.jsx
│   │   ├── Cart/
│   │   │   └── Carrito.jsx
│   │   ├── Contact/
│   │   │   └── Contacto.jsx
│   │   ├── Login/
│   │   │   └── Login.jsx
│   │   └── Admin/
│   │       └── AdminPanel.jsx
│   ├── hooks/                 # Custom React hooks
│   │   ├── useProductos.js
│   │   ├── useCarrito.js
│   │   ├── useLoginForm.js
│   │   ├── useRegisterForm.js
│   │   ├── useContacto.js
│   │   └── useFiltros.js
│   └── context/               # Global state management
│       └── CarritoContext.jsx
└── public/
    └── img/                   # Static assets

Routing Configuration

The application uses React Router for client-side navigation:
import { Routes, Route, useLocation } from "react-router-dom";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Inicio from "./pages/Home/Inicio";
import Contacto from "./pages/Contact/Contacto";
import Login from "./pages/Login/Login";
import AdminPanel from "./pages/Admin/AdminPanel";
import Carrito from "./pages/Cart/Carrito";

function App() {
  const location = useLocation();
  const isAdmin = location.pathname.startsWith("/admin");

  return (
    <div style={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
      {!isAdmin && <Header />}
      
      <main style={{ flex: 1, width: "100%" }}>
        <Routes>
          <Route path="/" element={<Inicio />} />
          <Route path="/contacto" element={<Contacto />} />
          <Route path="/carrito" element={<Carrito />} />
          <Route path="/login" element={<Login />} />
          <Route path="/admin" element={<AdminPanel />} />
        </Routes>
      </main>
      
      {!isAdmin && <Footer />}
    </div>
  );
}
The admin panel uses a different layout without the header and footer for a focused dashboard experience.

Custom Hooks Pattern

Huellitas uses custom hooks to encapsulate data fetching and state management logic:
// hooks/useProductos.js
import { useState, useEffect } from 'react';

export const useProductos = () => {
  const [productos, setProductos] = useState([]);
  const [cargando, setCargando] = useState(true);

  useEffect(() => {
    const obtenerProductos = async () => {
      try {
        const respuesta = await fetch('https://petshophuellitas.onrender.com/api/Productos');
        const datos = await respuesta.json();
        setProductos(datos);
        setCargando(false);
      } catch (error) {
        console.error("Error al cargar productos:", error);
        setCargando(false);
      }
    };

    obtenerProductos();
  }, []);
  
  return { productos, cargando };
};

State Management

The application uses a combination of:
  • Local component state for UI interactions
  • Context API for global cart state
  • LocalStorage for cart persistence
  • Custom hooks for data fetching and business logic

Backend Architecture (.NET API)

The backend follows a clean, layered architecture with clear separation of concerns.

Solution Structure

The .NET solution consists of four projects:
Purpose: HTTP endpoints and controllersResponsibilities:
  • Handle HTTP requests and responses
  • Route management
  • Input validation
  • Authentication and authorization
  • API documentation (Swagger)
Key Files:
Huellitas.API/
├── Controllers/
│   ├── ProductosController.cs
│   ├── PedidosController.cs
│   └── AuthController.cs
├── Program.cs              # App configuration
├── appsettings.json        # Configuration
└── Huellitas.API.csproj
Example Controller:
[Route("api/[controller]")]
[ApiController]
public class ProductosController : ControllerBase
{
    private readonly IProductoService _productoService;

    public ProductosController(IProductoService productoService)
    {
        _productoService = productoService;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Producto>>> GetProductos()
    {
        var productos = await _productoService.ObtenerProductosAsync();
        return Ok(productos);
    }

    [HttpGet("{idProducto}")]
    public async Task<ActionResult<Producto>> GetProducto(int id)
    {
        var producto = await _productoService.ObtenerProductoPorIdAsync(id);
        if (producto == null)
        {
            return NotFound("El producto no existe");
        }
        return Ok(producto);
    }

    [HttpPost]
    public async Task<ActionResult<Producto>> PostProducto(Producto producto)
    {
        try
        {
            var nuevoProducto = await _productoService.CrearProductoAsync(producto);
            return CreatedAtAction(nameof(GetProducto), 
                new { id = nuevoProducto.idProducto }, nuevoProducto);
        }
        catch (SystemException ex)
        {
            return BadRequest(ex.Message);
        }
    }
}
Purpose: Business rules and validationResponsibilities:
  • Implement business logic
  • Data validation
  • Error handling
  • DTO mapping
  • Service coordination
Key Files:
Huellitas.Service/
├── ProductoService.cs
├── PedidoService.cs
├── AuthService.cs
├── Interfaces/
│   ├── IProductoService.cs
│   ├── IPedidoService.cs
│   └── IAuthService.cs
└── Huellitas.Service.csproj
Example Service Interface:
public interface IProductoService
{
    Task<IEnumerable<Producto>> ObtenerProductosAsync();
    Task<Producto> ObtenerProductoPorIdAsync(int id);
    Task<Producto> CrearProductoAsync(Producto producto);
    Task ActualizarProductoAsync(int id, Producto producto);
    Task<bool> EliminarProductoAsync(int id);
}
Purpose: Database operations and contextResponsibilities:
  • Database context configuration
  • Repository implementations
  • Entity Framework migrations
  • Query optimization
  • Data access patterns
Key Files:
Huellitas.Data/
├── HuellitasContext.cs
├── Repositorios/
│   ├── ProductoRepositorio.cs
│   ├── PedidoRepositorio.cs
│   └── UsuarioRepositorio.cs
├── Migrations/
│   ├── 20251226222715_Inicial.cs
│   ├── 20251226224227_AgregandoTablasRestantes.cs
│   └── 20251229094729_AgregoDescripcion.cs
└── Huellitas.Data.csproj
Database Context:
using Huellitas.Core.Entities;
using Microsoft.EntityFrameworkCore;

namespace Huellitas.Data
{
    public class HuellitasContext : DbContext
    {
        public HuellitasContext(DbContextOptions<HuellitasContext> options) 
            : base(options)
        {
        }

        // DbSets represent database tables
        public DbSet<Producto> Productos { get; set; }
        public DbSet<Categoria> Categorias { get; set; }
        public DbSet<Usuario> Usuarios { get; set; }
        public DbSet<Rol> Roles { get; set; }
        public DbSet<Pedido> Pedidos { get; set; }
        public DbSet<Detalle> Detalles { get; set; }
    }
}
Purpose: Core entities and contractsResponsibilities:
  • Define domain entities
  • Interface contracts
  • Domain models
  • Shared constants
  • No dependencies on other layers
Key Files:
Huellitas.Core/
├── Entities/
│   ├── Producto.cs
│   ├── Categoria.cs
│   ├── Usuario.cs
│   ├── Rol.cs
│   ├── Pedido.cs
│   └── Detalle.cs
├── Interfaces/
│   ├── IProductoRepositorio.cs
│   ├── IPedidoRepositorio.cs
│   └── IUsuarioRepositorio.cs
└── Huellitas.Core.csproj
Example Entity:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Huellitas.Core.Entities
{
    [Table("producto")]
    public class Producto
    {
        [Key]
        public int idProducto { get; set; }
        
        [Required]
        [MaxLength(100)]
        public string nombre { get; set; } = string.Empty;
        
        [Column(TypeName = "decimal(18,2)")]
        public decimal precio { get; set; }
        
        public string img { get; set; } = string.Empty;
        public int stockActual { get; set; }
        public int stockMinimo { get; set; }
        public int idCategoria { get; set; }
        
        [ForeignKey("idCategoria")]
        public virtual Categoria Categoria { get; set; } = null!;
        
        public string descripcion { get; set; } = string.Empty;
    }
}

Dependency Injection Configuration

Services are registered in Program.cs using dependency injection:
using Microsoft.EntityFrameworkCore;
using Huellitas.Data;
using Huellitas.Core.Interfaces;
using Huellitas.Service;

var builder = WebApplication.CreateBuilder(args);

// Database context
builder.Services.AddDbContext<HuellitasContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

// Repository and Service registration
builder.Services.AddScoped<IProductoRepositorio, ProductoRepositorio>();
builder.Services.AddScoped<IProductoService, ProductoService>();
builder.Services.AddScoped<IUsuarioRepositorio, UsuarioRepositorio>();
builder.Services.AddScoped<IAuthService, AuthService>();

// CORS policy
builder.Services.AddCors(options =>
{
    options.AddPolicy("PermitirFrontend",
        policy =>
        {
            policy.AllowAnyOrigin()
                  .AllowAnyHeader()
                  .AllowAnyMethod();
        });
});

var app = builder.Build();

// Middleware pipeline
app.UseHttpsRedirection();
app.UseCors("PermitirFrontend");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Authentication & Authorization

Huellitas uses JWT (JSON Web Tokens) for secure authentication:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var jwtSettings = builder.Configuration.GetSection("Jwt");
var secretKey = jwtSettings["Key"];
var key = Encoding.ASCII.GetBytes(secretKey);

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = true,
        ValidIssuer = jwtSettings["Issuer"],
        ValidateAudience = true,
        ValidAudience = jwtSettings["Audience"],
        ValidateLifetime = true
    };
});

Database Architecture (PostgreSQL)

The database follows a normalized relational schema designed for data consistency and integrity.

Database Schema

Database Diagram

Main Tables

producto

Columns: idProducto (PK), nombre, precio, img, stockActual, stockMinimo, idCategoria (FK), descripcionStores product catalog with pricing, inventory, and categorization.

categoria

Columns: idCategoria (PK), nombre, descripcionProduct categories for organization and filtering.

usuario

Columns: idUsuario (PK), nombre, email, password, idRol (FK), fechaRegistroUser accounts with authentication credentials and role assignment.

rol

Columns: idRol (PK), nombre, descripcionUser roles for authorization (admin, customer, etc.).

pedido

Columns: idPedido (PK), idUsuario (FK), fechaPedido, total, estadoCustomer orders with status tracking.

detalle

Columns: idDetalle (PK), idPedido (FK), idProducto (FK), cantidad, precioUnitario, subtotalOrder line items linking products to orders.

Relationships

Entity Framework Code-First Approach

The database schema is defined through C# entity classes and created using migrations:
# Create a new migration
dotnet ef migrations add MigrationName

# Apply migration to database
dotnet ef database update

# Rollback migration
dotnet ef database update PreviousMigrationName
The code-first approach allows version control of database schema changes and makes team collaboration easier.

Data Flow

Let’s trace a complete request from the browser to the database:
1

User Action

User clicks on a product in the React app to view details
2

API Request

The useProductos hook makes an HTTP GET request:
const respuesta = await fetch(
  'https://petshophuellitas.onrender.com/api/Productos/123'
);
3

Controller Receives Request

The ProductosController handles the request:
[HttpGet("{idProducto}")]
public async Task<ActionResult<Producto>> GetProducto(int id)
{
    var producto = await _productoService.ObtenerProductoPorIdAsync(id);
    return Ok(producto);
}
4

Service Layer Processing

The ProductoService implements business logic:
public async Task<Producto> ObtenerProductoPorIdAsync(int id)
{
    var producto = await _productoRepositorio.ObtenerPorIdAsync(id);
    if (producto == null)
        throw new NotFoundException("Producto no encontrado");
    return producto;
}
5

Repository Data Access

The ProductoRepositorio queries the database:
public async Task<Producto> ObtenerPorIdAsync(int id)
{
    return await _context.Productos
        .Include(p => p.Categoria)
        .FirstOrDefaultAsync(p => p.idProducto == id);
}
6

Database Query

Entity Framework generates and executes SQL:
SELECT p.*, c.*
FROM producto p
INNER JOIN categoria c ON p.idCategoria = c.idCategoria
WHERE p.idProducto = 123;
7

Response Journey

Data flows back up the layers:
  1. Database returns rows
  2. EF Core maps to Producto entity
  3. Repository returns entity to service
  4. Service returns to controller
  5. Controller serializes to JSON
  6. HTTP response sent to client
8

UI Update

React receives JSON and updates the component:
const datos = await respuesta.json();
setProductos(datos);

Design Patterns

Huellitas implements several industry-standard design patterns:

Repository Pattern

Purpose: Abstracts data access logic and provides a collection-like interface for domain entities. Benefits:
  • Decouples business logic from data access
  • Makes testing easier (mock repositories)
  • Centralizes data access logic
  • Provides consistent API across different data sources

Dependency Injection

Purpose: Inverts control by injecting dependencies rather than creating them. Benefits:
  • Improves testability
  • Reduces coupling between components
  • Makes code more maintainable
  • Supports configuration-based behavior

DTO (Data Transfer Object)

Purpose: Transfers data between layers without exposing internal structure. Benefits:
  • Protects sensitive information
  • Reduces over-fetching
  • Allows independent evolution of layers
  • Optimizes network payload

Service Layer Pattern

Purpose: Encapsulates business logic separate from controllers and repositories. Benefits:
  • Centralizes business rules
  • Promotes code reuse
  • Simplifies testing
  • Clear separation of concerns

Performance Considerations

Async/Await

All I/O operations use async/await for non-blocking execution and better scalability.

Entity Framework Optimization

Strategic use of .Include() for eager loading and avoiding N+1 query problems.

React Hooks Memoization

Custom hooks optimize re-renders and prevent unnecessary API calls.

LocalStorage Caching

Cart data persists in browser storage to reduce server requests.

Security Architecture

  • Stateless token-based authentication
  • Tokens contain user claims and roles
  • Configurable expiration times
  • Secure key management via configuration
  • Controlled cross-origin access
  • Configurable allowed origins
  • Header and method restrictions
  • Production-ready policy
  • All communication over encrypted connections
  • Automatic HTTP to HTTPS redirection
  • Secure cookie flags
  • Data annotations on entities
  • Controller-level validation
  • Service-layer business rule validation
  • SQL injection prevention via EF parameterization

Deployment Architecture

Hosting Details

Frontend

Platform: GitHub PagesType: Static site hostingCDN: Global edge networkDeployment: Automated via npm run deploy

Backend

Platform: RenderType: Container-based hostingScaling: AutomaticDeployment: Git push to deploy

Database

Platform: NeonType: Managed PostgreSQLBackup: AutomatedScaling: Serverless

Scalability & Extensibility

The architecture is designed to support future growth:
The layered architecture makes it easy to:
  • Add new features without breaking existing code
  • Replace implementations (e.g., switch databases)
  • Scale horizontally by deploying multiple API instances
  • Add caching layers (Redis, CDN)
  • Implement microservices for specific domains

Next Steps

Now that you understand the architecture:

Explore the Code

Clone the repository and examine the implementation details

API Documentation

Visit /swagger on the backend to see interactive API docs

Contribute

Submit issues or pull requests on GitHub

Build Features

Use this architecture as a foundation for your own features

This documentation reflects the current state of the project. As Huellitas evolves with new features like ML recommendations and predictive analytics, the architecture will be updated accordingly.

Build docs developers (and LLMs) love