尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Interfaz web de Finova, un SaaS de contabilidad y automatización tributaria para el mercado chileno: importa Documentos Tributarios Electrónicos (DTE) desde el SII, arma una propuesta de Formulario 29 y revisa inconsistencias con un copiloto de IA antes de declarar.
Este repositorio contiene solo el frontend. La lógica de negocio, la base de
datos y la integración con el SII viven en finova-backend (NestJS + PostgreSQL
Estado del proyecto: la landing, el acceso y el núcleo contable (libro diario, asientos, períodos y plan de cuentas) funcionan contra el backend real. Documentos, terceros, F29, copiloto y auditoría siguen como pantallas pendientes. Ver Estado real de cada ruta.
| Pieza | Versión | Nota |
|---|---|---|
| Next.js | 16.3.4 | App Router. Ojo: Next 16 renombró middleware.ts a proxy.ts y cambió varias APIs respecto de Next 14/15. |
| React | 19.2.8 | Server Components por defecto. |
| TypeScript | 5.x | strict: true. Alias @/* apunta a la raíz. |
| Tailwind CSS | 4.x | Configuración en CSS, no en tailwind.config.js. Los tokens viven en @theme dentro de app/globals.css. |
| ESLint | 9.x | Flat config (eslint.config.mjs) con core-web-vitals + typescript. |
| Node | ≥ 20 (probado en 24) | Vercel usa 20/22 según lo que configures. |
Sin dependencias de UI de terceros. No hay Radix, shadcn, framer-motion ni librerías de iconos: todo —iconos, animaciones, acordeón del FAQ, revelado al hacer scroll— está construido a mano con CSS y APIs del navegador. Eso mantiene el bundle pequeño y el control total sobre la accesibilidad.
# 1. Dependencias
npm install
# 2. Variables de entorno
cp .env.example .env.local # y ajusta los valores
# 3. Servidor de desarrollo
npm run dev # http://localhost:3000
| Script | Qué hace |
|---|---|
npm run dev | Servidor de desarrollo con recarga en caliente. |
npm run build | Compilación de producción. Ejecútalo antes de cada push: es lo mismo que corre Vercel, y falla ante cualquier error de tipos. |
npm start | Sirve la compilación de producción localmente. |
npm run lint | ESLint sobre todo el proyecto. |
Ambos usan el puerto 3000 por defecto, así que chocan. La convención de este proyecto es dejar Next en 3000 y mover Nest a 3001:
# Terminal 1 — base de datos
cd ../finova-backend && docker compose up -d
# Terminal 2 — API
cd ../finova-backend && PORT=3001 npm run start:dev
# Terminal 3 — frontend
npm run dev
En PowerShell, la segunda terminal es $env:PORT = "3001"; npm run start:dev.
La plantilla completa y comentada está en .env.example.
Resumen:
| Variable | Ámbito | Para qué |
|---|---|---|
NEXT_PUBLIC_API_URL | Navegador + servidor | URL pública del backend NestJS, sin barra final. |
API_URL_INTERNA | Solo servidor | Opcional. Ruta privada al backend para componentes de servidor. |
CSP_CONNECT_SRC | Build | Orígenes que la Content Security Policy autoriza en connect-src. Sin esto, el navegador bloquea todo fetch al backend en producción. |
NEXT_PUBLIC_SITE_URL | Navegador | Opcional. URL canónica para las etiquetas Open Graph. |
Regla de oro: todo lo que lleve el prefijo
NEXT_PUBLIC_queda incrustado en el JavaScript que descarga el navegador y es público. Claves de API de LLM, credenciales del SII o secretos de base de datos nunca llevan ese prefijo — de hecho, no deberían estar en este repositorio en absoluto: van en el backend.
finova-frontend/
├── app/ Rutas (App Router)
│ ├── layout.tsx Layout raíz: fuentes, metadata, script de tema
│ ├── globals.css Tokens de diseño + animaciones (Tailwind v4)
│ ├── favicon.ico Icono 16/32/48/64 — Next lo enlaza solo
│ ├── icon.png Icono 256 px para navegadores modernos
│ ├── apple-icon.png Icono 180 px para iOS
│ ├── page.tsx Landing pública — solo composición de secciones
│ ├── login/page.tsx Pantalla de acceso (dos columnas)
│ ├── dashboard/
│ │ ├── layout.tsx Layout privado: sidebar + cabecera
│ │ └── page.tsx Panel de control con cifras reales del libro
│ ├── empresas/page.tsx ┐
│ ├── documentos/page.tsx │
│ ├── core-contable/page.tsx ├ Placeholders de 7 líneas, uno por épica
│ ├── f29/page.tsx │
│ ├── copiloto/page.tsx │
│ └── auditoria/page.tsx ┘
│
├── components/
│ ├── landing/ Una sección de la landing por archivo
│ │ ├── SiteHeader.tsx Cabecera con navegación y ThemeToggle
│ │ ├── HeroSection.tsx Titular + animación del ciclo DTE → F29
│ │ ├── AsciiBanner.tsx Marquesina ASCII de módulos contables
│ │ ├── AboutSection.tsx Qué es Finova
│ │ ├── FeaturesSection.tsx Capacidades del producto
│ │ ├── ProcessSequence.tsx Secuencia animada del proceso contable
│ │ ├── PricingSection.tsx Planes
│ │ ├── TrustSection.tsx Señales de confianza
│ │ ├── FaqSection.tsx Acordeón accesible
│ │ └── SiteFooter.tsx Pie con enlaces y aviso legal
│ ├── login/LoginForm.tsx Formulario de credenciales (cliente)
│ ├── Sidebar.tsx Navegación del área privada
│ └── ui/
│ ├── Icon.tsx 14 iconos SVG propios, tipados por nombre
│ ├── InlineScript.tsx Inyecta el script anti-parpadeo en <head>
│ ├── Reveal.tsx Revelado al entrar en viewport
│ ├── SectionShell.tsx Contenedor común de las secciones
│ └── ThemeToggle.tsx Botón de tema claro/oscuro
│
├── lib/
│ ├── api.ts Cliente HTTP hacia el backend NestJS
│ ├── fonts.ts Space Grotesk + Inter vía next/font
│ ├── theme.ts Fuente única de verdad del tema
│ └── validation.ts Validadores puros del formulario de acceso
│
├── docs/
│ ├── LANDING.md Documentación detallada de la landing
│ └── DESPLIEGUE-VERCEL.md Plan paso a paso de despliegue
│
├── images/ Mockups de referencia (NO se sirven)
├── public/ Assets servidos: logo, foto de login, SVG
├── next.config.ts Cabeceras de seguridad y CSP
├── .env.example Plantilla de variables de entorno
├── CLAUDE.md Contexto de producto para agentes de IA
└── AGENTS.md Aviso de Next 16 (autogenerado por `next dev`)
images/ vs public/Son cosas distintas y se confunden con facilidad. public/ se sirve tal cual en
la raíz del sitio: cualquiera puede abrir /logo-finova.png. images/ guarda
los mockups de referencia del diseño y no se publica. No muevas archivos
entre ambas sin pensar en eso.
| Ruta | Estado | Detalle |
|---|---|---|
/ | ✅ Terminada | Landing de 8 secciones, tema claro/oscuro, animaciones CSS. |
/login | ✅ Real | Autentica contra el backend; el JWT queda en una cookie httpOnly. |
/dashboard | ✅ Real | Cifras clave del libro (efectivo, por cobrar, por pagar, resultado del ejercicio), alertas reales (IVA estimado del F29 del mes anterior, meses sin cerrar, período del mes, borradores, plan vacío), ingresos y gastos por mes y últimos asientos. Sale de GET /asientos-contables/resumen y de períodos, borradores y asientos. |
| Popup de empresas (cabecera) | ✅ Real | Cambiar la empresa activa, agregar (administrador o contador), cambiar el nombre (solo la razón social; administrador de esa empresa) y eliminar con doble confirmación: papelera y escribir el RUT. Reemplaza a la antigua página /dashboard/empresas. |
/dashboard/core-contable | ✅ Real | Libro diario: comprobantes con filtros y paginación, vista de libro imprimible con resumen por día, borradores. |
/dashboard/core-contable/nuevo y /borradores/[id] | ✅ Real | Formulario de asiento: líneas dinámicas, cuadre en vivo con aritmética exacta (lib/decimal.ts), guardar borrador y contabilizar con confirmación. |
/dashboard/core-contable/[id] | ✅ Real | Detalle del comprobante y reversión (art. 32 del Código de Comercio). |
/dashboard/periodos | ✅ Real | Abrir mes, cerrar (administrador o contador) y reabrir con motivo (solo administrador). |
/dashboard/plan-cuentas | ✅ Real | Árbol del plan con crear, editar, eliminar (popup de confirmación) y exportar CSV. configurar/: "Configurar plan de cuentas" elige las cuentas del plan base NIIF y agrega cuentas propias (administrador o contador). |
/dashboard/documentos, /terceros, /f29, /copiloto, /auditoria | 🚧 Placeholder | PantallaPendiente con los endpoints que usará cada una. |
Los botones que registran (nuevo asiento, revertir, cerrar período) se ocultan
al rol "consulta" leyendo el rol del JWT (obtenerRolDelToken). Es solo
presentación: el backend responde 403 igual si alguien fuerza la llamada.
La paleta parte de una idea concreta y no de un gusto: son colores de destacador, el lápiz con que un contador marca un formulario en papel.
Ningún fondo es blanco ni negro puro. El blanco puro deslumbra en una
herramienta donde se pasan horas revisando montos; el negro puro genera halo
contra texto claro y arrastre al hacer scroll en pantallas OLED. Ambos fondos
comparten temperatura cálida a propósito: con un fondo neutro frío el naranjo
de acento se veía sucio. En oscuro la superficie elevada es #1E1A17 y el
texto #F4F1EC; en claro, #F1EBE1 y #14110F.
| Token | Valor | Uso |
|---|---|---|
--color-ink | #14110F | Tinta café: texto en claro, fondo en oscuro |
--color-paper | #FAF7F2 | Papel cálido: fondo en claro |
--color-bone | #F1EBE1 | Papel sombreado: superficies elevadas en claro |
--color-cream | #F7F4EC | Barra de navegación |
--color-marker | #FF6A1A | Acento principal, botones |
--color-marker-soft | #FF8642 | El mismo naranjo en tema oscuro (el puro vibra sobre negro) |
--color-highlight | #FFC53D | Resaltado de texto |
--color-graphite | #6E6459 | Gris cálido: texto secundario |
Tipografía: Space Grotesk para titulares (font-display), Inter para cuerpo
e interfaz (font-sans). Ambas autohospedadas por next/font, sin petición a
Google. El brief original pedía Styrene, que es comercial y requiere
licencia; lib/fonts.ts documenta la migración exacta —tres líneas— si algún
día se compra.
Tailwind v4 se configura en CSS. No busques tailwind.config.js: no existe.
Los tokens se declaran con @theme en app/globals.css, y el dark: se activa
por clase mediante @custom-variant dark, no por prefers-color-scheme.
Es la parte menos obvia del proyecto y conviene entenderla antes de tocarla.
El servidor no puede saber qué tema eligió la persona: localStorage solo
existe en el navegador. Si React resolviera el tema al hidratar, el navegador ya
habría pintado la página en claro y se vería un destello blanco antes de pasar a
oscuro.
La solución tiene tres piezas que dependen entre sí:
lib/theme.ts exporta THEME_INIT_SCRIPT, un script minificado que lee
localStorage, cae a la preferencia del sistema si no hay nada válido, y
aplica la clase .dark más color-scheme sobre <html>.app/layout.tsx lo inyecta en <head> con InlineScript, de modo que
corre de forma síncrona durante el parseo, antes del primer paint.suppressHydrationWarning en <html> es obligatorio. Sin él, React
detecta que el DOM no coincide con lo que renderizó el servidor, lo trata
como error y vuelve a renderizar en el cliente descartando la corrección del
script — devolviendo justo el parpadeo que se quería evitar.Si cambias la clave de localStorage o el nombre de la clase, hazlo solo en
lib/theme.ts: es la fuente única de verdad, y el ThemeToggle lee de ahí.
next.config.ts aplica seis cabeceras a todas las rutas:
| Cabecera | Qué previene |
|---|---|
Strict-Transport-Security | Degradación a HTTP en la primera petición. |
X-Frame-Options: DENY | Clickjacking mediante iframe. |
X-Content-Type-Options: nosniff | Que el navegador adivine el tipo de un archivo. |
Referrer-Policy | Fuga de rutas internas al salir hacia otro dominio. |
Permissions-Policy | Acceso a cámara, micrófono, ubicación, pagos y USB. |
Content-Security-Policy | Ejecución de scripts y conexiones no autorizadas. |
Además, poweredByHeader: false quita la cabecera que anuncia el framework y su
versión.
Sobre unsafe-inline en script-src: está aceptado a conciencia. El script
anti-parpadeo es inline por definición y no interpola nada controlado por el
usuario. El endurecimiento posible —hash SHA-256 del script— está documentado en
los comentarios de next.config.ts, con su costo de mantenimiento explicado.
La validación del formulario es de experiencia de usuario, no de seguridad.
lib/validation.ts lo dice en su encabezado: corre en el navegador y cualquiera
la desactiva. Cuando exista autenticación real, el backend NestJS debe volver a
validar todo desde cero tratando cada campo como entrada hostil.
Otras decisiones deliberadas en LoginForm.tsx:
action y llama a preventDefault(): un <form> sin
action hace GET a la URL actual, lo que dejaría la contraseña en la barra de
direcciones, el historial y los logs del servidor.localStorage,
cookies ni URL, y se limpia al fallar.spellCheck desactivado: algunos correctores envían el texto a un servicio
remoto.Todo el tráfico hacia NestJS pasa por lib/api.ts. No llames a fetch()
directamente desde un componente: la URL base, el timeout, el manejo de errores
y el envío de credenciales quedarían repetidos y divergiendo.
import { empresasApi, ApiError } from "@/lib/api";
// Componente de servidor
export default async function EmpresasPage() {
try {
const empresas = await empresasApi.listar();
return <ListaEmpresas empresas={empresas} />;
} catch (error) {
if (error instanceof ApiError) {
return <p>No se pudieron cargar las empresas.</p>;
}
throw error;
}
}
Para rutas ad hoc está el cliente genérico:
import { api } from "@/lib/api";
const propuesta = await api.get<PropuestaF29>("/f29/2026-08");
await api.post("/documentos/importar", { periodo: "2026-08" });
Lo que lib/api.ts te da y fetch no:
AbortController, encadenado con cualquier signal
que le pases. Sin esto, una pestaña queda colgada indefinidamente si el
contenedor del backend está caído.ApiError con status y cuerpo crudo, para que la interfaz decida qué
mostrar sin filtrar detalles internos del servidor.credentials: "include", para cookies entre dominios distintos.empresasApi), de modo que TypeScript avise
cuando el backend cambie.verificarBackend(), útil para diagnosticar un despliegue: si eso falla,
ninguna otra llamada va a funcionar.El backend expone hoy un solo módulo (
/empresas) y su entidadEmpresaes una clase vacía. La interfazEmpresadelib/api.tses la forma esperada y hay que sincronizarla cuando se defina la entidad real.
El plan completo, paso a paso, está en
docs/DESPLIEGUE-VERCEL.md.
En una línea: el frontend va a Vercel sin ninguna configuración especial, pero
el backend no puede ir a Vercel tal como está (es un servidor persistente
con conexiones a PostgreSQL, no una función serverless). Necesita un proveedor
con contenedores. Hay tres puntos que rompen el despliegue si se olvidan: CORS
en NestJS, CSP_CONNECT_SRC en Vercel, y HTTPS en la API.
"use client" solo donde hay estado,
efectos o eventos: LoginForm, Reveal, ThemeToggle, SiteHeader.
Recuerda que "use client" marca un módulo, no un subárbol: las secciones
pasadas como children desde un componente de servidor siguen siendo de
servidor.@/ para toda importación entre carpetas; nada de ../../.empresasApi.listar), consistentes con el
backend y con el lenguaje del negocio.Nada de esto está oculto, y conviene atacarlo antes de mostrar el proyecto:
| # | Asunto | Impacto |
|---|---|---|
| 1 | Los módulos (/empresas, /f29, …) están fuera de app/dashboard/, así que se ven sin sidebar. | Alto — la navegación se rompe al primer clic. |
| 2 | No hay ninguna llamada real al backend todavía; lib/api.ts está listo pero sin usar. | Alto — es el siguiente paso funcional. |
| 3 | No hay autenticación: /dashboard es accesible sin sesión. | Alto — antes de cualquier dato real. |
| 4 | Los testimonios (María Fernández y los de la landing) son personas ficticias. | Legal — hay que reemplazarlos o etiquetarlos como ejemplo antes de publicar. |
| 5 | mk.tmp.py es un script temporal versionado por accidente. | Bajo — borrarlo. |
| 6 | No hay pruebas. El equipo definió Jest como herramienta de QA. | Medio. |
| 7 | El backend tiene credenciales de PostgreSQL escritas fijas en app.module.ts y synchronize: true. | Crítico en producción — pero se corrige en el otro repositorio. |
Proyecto Capstone (APT), Ingeniería en Informática, sede Plaza Vespucio.
| Persona | Rol |
|---|---|
| Pedro Ahumada | Product Owner · Lógica de backend · QA |
| Nicolás Sazo | Scrum Master · Arquitectura de base de datos |
| Vicente San Martín | Development Team · Frontend e integración |
Metodología Scrumban. El backlog se gestiona en Jira (proyecto KAN) con seis épicas: Core Contable, Motor Tributario y F29, Ingesta de DTE, Usuarios y Empresas, Copiloto con IA, y Auditoría y Seguridad.
Contexto ampliado de producto y alcance del MVP: CLAUDE.md.
Documentación de la landing: docs/LANDING.md.