Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Danielings/Pasantia-Proyecto/llms.txt

Use this file to discover all available pages before exploring further.

Equipment is the core entity of the inventory system. Each record represents a physical PC or Laptop together with its internal components (processor, RAM, hard drive, motherboard) and any peripherals attached at registration time. The backend enforces serial uniqueness across the entire system using a dedicated indices Firestore collection, and every write that involves serials runs inside a Firestore transaction to prevent race conditions.

Equipment Types

The system recognizes two equipment types, defined in inventory.constants.js:
export const EQUIPOS_MAP = {
  pc:     "PC",
  laptop: "Laptop",
};
Incoming tipo values are normalized (lowercased, trimmed) before lookup, so "PC", "pc", and " PC " all resolve to the canonical string "PC".

Internal Components

Every equipment document may carry an array of componentes. The four supported component types are:
export const COMPONENTES_MAP = {
  procesador:   "Procesador",
  memoria_ram:  "Memoria_RAM",
  disco_duro:   "Disco_Duro",
  motherboard:  "Motherboard",
};
Each component object within the array contains:
FieldRequiredDescription
tipoOne of the four canonical types above
marcaManufacturer name
modeloModel string
serialUnique serial number
estadoFunctional status
capacidad✅ for Memoria_RAM and Disco_DuroStorage or memory capacity (e.g., "16GB", "1TB"). Required when tipo is Memoria_RAM or Disco_Duro; omit for other component types.

Dual Location Fields

Every equipment record stores two separate location objects:
  • procedencia — where the asset originated (warehouse, supplier, former office). This field is set at registration and is never overwritten by subsequent edits to asignacion.
  • asignacion — where the asset is currently deployed (office, floor, wing). This is the field updated when equipment is moved.
Both objects share the same six-field structure: region, estado, ciudad, sede, piso, and the optional ala.

Validation Schema

The frontend validates equipment forms with Zod before sending data to the API. Key fields from equipoSchema.ts:
import { z } from "zod";

export const equipoSchema = z.object({
  type:   z.string().min(1, "El tipo de equipo es requerido"),
  name:   z.string().min(1, "La marca es requerida"),
  model:  z.string().min(1, "El modelo es requerido"),
  serial: z.string().min(1, "El serial del equipo principal es requerido"),
  status: z.string().min(1, "El estado es requerido"),

  // Procedencia (origin location)
  regionP: z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Región de procedencia requerida")),
  estadoP: z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Estado de procedencia requerido")),
  cityP:   z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Ciudad de procedencia requerida")),
  sedeP:   z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Sede de procedencia requerida")),
  pisoP:   z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Piso de procedencia requerido")),

  // Asignacion (current deployment location)
  region: z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Región de asignación requerida")),
  estado: z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Estado de asignación requerido")),
  city:   z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Ciudad de asignación requerida")),
  sede:   z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Sede de asignación requerida")),
  piso:   z.preprocess((val) => val == null ? "" : String(val),
    z.string().min(1, "Piso de asignación requerido")),
});
The z.preprocess wrappers convert null and undefined select values (common with controlled <select> components) to empty strings so Zod’s .min(1) check can produce a human-readable error.

Registering a PC

POST /api/pc — no authentication required for registration.
1

Prepare the request body

Build a JSON object with the equipment fields, both location objects, a componentes array, and an optional perifericos array.
2

Submit the request

Send the payload to POST /api/pc. The handler normalizes all inputs, then opens a Firestore transaction to validate serial uniqueness for the equipment, each component, and each peripheral before writing anything.
3

Receive the document ID

On success the API returns 201 Created with the new Firestore document ID.
POST /api/pc
Content-Type: application/json

{
  "marca": "Dell",
  "modelo": "OptiPlex 7090",
  "serial": "DL-OPT-00142",
  "estado": "Funcional",
  "notas": "Assigned to IT department",
  "procedencia": {
    "region": "Capital",
    "estado": "Distrito Capital",
    "ciudad": "Caracas",
    "sede": "Sede Central",
    "piso": "1"
  },
  "asignacion": {
    "region": "Capital",
    "estado": "Distrito Capital",
    "ciudad": "Caracas",
    "sede": "Torre Norte",
    "piso": "4",
    "ala": "Este"
  },
  "componentes": [
    {
      "tipo": "Procesador",
      "marca": "Intel",
      "modelo": "Core i7-10700",
      "serial": "CPU-10700-0042",
      "estado": "Funcional"
    },
    {
      "tipo": "Memoria_RAM",
      "marca": "Kingston",
      "modelo": "DDR4 16GB",
      "serial": "RAM-KVR-0099",
      "estado": "Funcional",
      "capacidad": "16GB"
    },
    {
      "tipo": "Disco_Duro",
      "marca": "Seagate",
      "modelo": "Barracuda 1TB",
      "serial": "HDD-SG-7734",
      "estado": "Funcional",
      "capacidad": "1TB"
    },
    {
      "tipo": "Motherboard",
      "marca": "Dell",
      "modelo": "OptiPlex Board",
      "serial": "MB-DL-0055",
      "estado": "Funcional"
    }
  ],
  "perifericos": [
    {
      "tipo": "Monitor",
      "marca": "LG",
      "modelo": "27MK400H",
      "serial": "MON-LG-0312",
      "estado": "Funcional"
    }
  ]
}
Success response:
HTTP/1.1 201 Created

{
  "message": "PC registrada con éxito.",
  "id": "3fKqL9mXzRpT2vAoW8nY"
}

Registering a Laptop

POST /api/laptop accepts exactly the same request body shape as POST /api/pc. The only difference is the resulting tipo field stored in Firestore, which will be "Laptop" instead of "PC".
POST /api/laptop
Content-Type: application/json

{
  "marca": "Lenovo",
  "modelo": "ThinkPad E15",
  "serial": "LNV-E15-00078",
  "estado": "Funcional",
  "procedencia": { "region": "Zuliana", "estado": "Zulia", "ciudad": "Maracaibo", "sede": "Sede Maracaibo", "piso": "2" },
  "asignacion":  { "region": "Zuliana", "estado": "Zulia", "ciudad": "Maracaibo", "sede": "Torre Lago", "piso": "3" },
  "componentes": [],
  "perifericos": []
}

Serial Uniqueness Enforcement

Before any document is written, the transaction calls validateUniqueSerial for every serial in the payload — the equipment serial, each component serial, and each peripheral serial. This function reads from the indices collection using a deterministic document ID built with serialIndexId(prefix, serial). If any index document already exists, the entire transaction is aborted and a 400 Bad Request is returned.
indices/equipo_{sha1(normalizedSerial)}
indices/componente_{sha1(normalizedSerial)}
indices/periferico_{sha1(normalizedSerial)}
Duplicate serials within the same request are also caught before the transaction begins, via validatePayloadDuplicates(). If you send two components with the same serial in one request the API returns 400 immediately without touching Firestore.

Querying Equipment

List all equipment

GET /api/equipos — requires authentication (verificarToken middleware). Supports three optional query parameters for client-side filtering:
ParameterDescriptionExample
tipoFilter by equipment type?tipo=Laptop
estadoFilter by status?estado=Funcional
serialExact match on serial (normalized)?serial=DL-OPT-00142
Non-Superadministrador users automatically receive only records where asignacion.sede matches their assigned office (torre).

Get a single equipment record

GET /api/equipos/:id
Returns the full Firestore document including nested componentes and perifericos arrays.

Get a single equipment record (alternate route)

GET /api/equipo/:id
An alternate route that returns the same full equipment document. The response explicitly guarantees componentes and perifericos default to empty arrays when absent from the Firestore document:
{
  "id": "3fKqL9mXzRpT2vAoW8nY",
  "tipo": "PC",
  "marca": "Dell",
  "modelo": "OptiPlex 7090",
  "serial": "DL-OPT-00142",
  "estado": "Funcional",
  "componentes": [],
  "perifericos": []
}

List equipment summaries

GET /api/equipos/lista — no authentication required. Returns a lightweight array containing only id, serial, marca, modelo, and tipo for every equipment record. Use this endpoint to populate equipment selector dropdowns without fetching full documents.
[
  {
    "id": "3fKqL9mXzRpT2vAoW8nY",
    "serial": "DL-OPT-00142",
    "marca": "Dell",
    "modelo": "OptiPlex 7090",
    "tipo": "PC"
  }
]

Search by serial number

GET /api/buscar/:serial
Performs a full collection scan and returns the first document whose normalized serial matches the path parameter. Returns 404 if no match is found.
GET /api/buscar/DL-OPT-00142

{
  "id": "3fKqL9mXzRpT2vAoW8nY",
  "tipo": "PC",
  "marca": "Dell",
  "modelo": "OptiPlex 7090",
  "serial": "DL-OPT-00142",
  "estado": "Funcional",
  "procedencia": { ... },
  "asignacion": { ... },
  "componentes": [ ... ],
  "perifericos": [ ... ]
}

Editing Equipment

PUT /api/equipos/:id — updates an existing equipment record inside a Firestore transaction.
The PUT handler merges incoming component data with existing component data using the serial number as the key. For each component in the request body:
  • If a component with the same serial already exists in the stored componentes array, the handler spreads the old object first, then the new object on top ({ ...old, ...new }). Fields you omit in the request are preserved from the existing record.
  • If the serial is new, the component is appended as-is.
This means a partial update — sending only { serial: "CPU-10700-0042", estado: "En Reparación" } — will update just estado while keeping all other fields intact.
The PUT handler computes three sets from the old and new peripheral serial lists:
  • Removed serials: The matching document in the perifericos collection is updated to asignado: false, and equipoId / equipoSerial are cleared.
  • Added serials: If the peripheral already exists as a standalone record and is not assigned elsewhere, it is linked to this equipment. If it does not exist at all, a new perifericos document is created and marked as assigned.
  • Kept serials: The equipoRelacionado back-reference is refreshed to reflect any changes to the equipment’s own fields (e.g., updated serial or model).
PUT /api/equipos/3fKqL9mXzRpT2vAoW8nY
Content-Type: application/json

{
  "estado": "En Reparación",
  "componentes": [
    {
      "tipo": "Procesador",
      "marca": "Intel",
      "modelo": "Core i7-10700",
      "serial": "CPU-10700-0042",
      "estado": "En Reparación"
    }
  ],
  "perifericos": []
}

Build docs developers (and LLMs) love