Proyecto 01: Gestión de presupuestos para una empresa de cerramientos
ENCARGO · 2026Una 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.




- 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";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;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];
}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.