Saltar al contenido
Erik Portillo
Volver a Trabajo

Proyecto 01: Gestión de presupuestos para una empresa de cerramientos

ENCARGO · 2026

Una aplicación interna para que una empresa de cerramientos pase de la visita en casa del cliente al presupuesto, con la medición hecha en el móvil.

Tipo
Encargo para una empresa de cerramientos
Año
2026
Mi papel
Diseño y desarrollo completo: interfaz, servidor, base de datos e integraciones
Código
Privado, del cliente

Next.js 15 · TypeScript · Prisma · PostgreSQL · NextAuth · React Hook Form · Zod · Playwright

El contexto

El trabajo de la empresa empieza en casa del cliente: se mide la obra (un cerramiento, un techo, una ventana) y, con esas medidas y el precio del proveedor, se prepara un presupuesto.

La aplicación reúne ese recorrido en un solo sitio: cada medición, cada foto y cada presupuesto quedan asociados a su cliente y a su obra, y cada persona ve lo que le corresponde según su rol.

Cómo funciona

De la visita a la oficina, en el orden en que lo usa el equipo.

Asistente de nueva visita, paso 3 de 6: elegir el tipo de trabajo de la obra
1Visita guiadaUn asistente de seis pasos (cliente, obra, tipo de trabajo, mediciones, fotos y presupuesto) guía al medidor durante la visita. Cada paso parte de lo elegido en el anterior.
Formulario de medición: elegir la forma del cerramiento
2Medición por formaPrimero se elige el producto y la forma del cerramiento: frontal, en L, en U, perimetral o personalizada. La forma decide qué tramos hay que medir.
Tramo de un cerramiento: ancho y alto en milímetros con sus límites, apertura, vidrio, color y puerta
3Tramos con límitesCada tramo pide ancho y alto en milímetros, con sus límites mínimo y máximo, tipo de apertura, vidrio, color RAL y si incluye puerta. Los datos para el pedido al proveedor quedan en un bloque aparte.
Resumen de la medición: forma, número de tramos, metros lineales y superficie
4Resumen calculadoCon cada tramo, la aplicación recalcula metros lineales y superficie. Ese resumen acompaña a la medición hasta administración.
  1. 5

    Presupuesto

    Administración crea el presupuesto desde la medición: coste del proveedor (introducido a mano, importado desde su PDF o pedido a su portal), horas de trabajo, otros costes, margen e IVA. El resultado se exporta en PDF para el cliente.

Decisiones técnicas

Los proveedores, detrás de un adaptador con tres modos

Cada proveedor implementa el mismo adaptador. El acceso a sus portales con Playwright funciona, pero el precio automático todavía no es fiable en todos los casos, así que cada consulta declara su modo (manual, simulado o real) y su resultado (completo, parcial o fallido).

El presupuesto nunca depende de que el portal responda: existe el modo manual y, cuando el precio automático falla, un respaldo explícito en modo simulado.

export type ProviderMode = "manual" | "mock" | "real";
export type ProviderResultStatus = "complete" | "partial" | "failed";
Tipos reales del adaptador de proveedores.

Un presupuesto no cambia si sube la tarifa

El cálculo es una función pura, separada de la configuración. Al crear un presupuesto se guarda en la auditoría el coste por hora usado; al editarlo, el servidor recupera ese valor en lugar de la tarifa actual.

Así los presupuestos ya enviados no cambian cuando la empresa actualiza sus precios. Si no hay instantánea, se usa la tarifa vigente.

const auditRate = auditData?.snapshot?.hourCostRate;
const hourCostOverride =
  typeof auditRate === "number" && Number.isFinite(auditRate) && auditRate > 0
    ? auditRate
    : config.hourCost;
Al editar un presupuesto: coste por hora recuperado de la auditoría.

Mediciones con plantilla versionada

Los datos de cada medición se guardan junto con el identificador y la versión de la plantilla que los generó. El formulario puede evolucionar sin romper las mediciones antiguas, y los campos pueden mostrarse u ocultarse según otros valores.

Permisos por nivel y auditoría

Cuatro roles (medición, administración, administrador y superadministrador) con un nivel numérico. Las páginas y el API consultan funciones de permiso concretas en lugar de comparar roles sueltos.

Las acciones relevantes se registran con usuario, entidad y una instantánea de los datos.

const ROLE_LEVEL: Record<Role, number> = {
  MEASURER: 1,
  ADMINISTRATION: 2,
  ADMIN: 3,
  SUPERADMIN: 4,
};

export function hasRole(userRole: Role, required: Role): boolean {
  return ROLE_LEVEL[userRole] >= ROLE_LEVEL[required];
}
Niveles de rol, tal como están en el código.

Estado y límites

  • La integración con los portales de proveedores es parcial: el acceso funciona, el precio automático no siempre. Por eso existen los modos simulado y manual.
  • Las capturas salen de una copia local con datos de demostración; ningún dato del cliente.