snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
---
title: Mejores prácticas
---
Este documento describe las mejores prácticas que debe seguir al trabajar en el backend.
## Siga un enfoque modular
El backend sigue un enfoque modular, que es un principio fundamental al trabajar con NestJS. Asegúrese de descomponer su código en módulos reutilizables para mantener una base de código limpia y organizada.
Cada módulo debe encapsular una característica o funcionalidad particular y tener un alcance bien definido. Este enfoque modular permite una clara separación de responsabilidades y elimina complejidades innecesarias.
## Exponer servicios para usar en módulos
Siempre cree servicios que tengan una responsabilidad clara y única, lo que mejora la legibilidad y mantenibilidad del código. Nombre los servicios de manera descriptiva y consistente.
También debe exponer servicios que desee usar en otros módulos. Exponer servicios a otros módulos es posible a través del poderoso sistema de inyección de dependencias de NestJS, y promueve un acoplamiento débil entre los componentes.
## Evitar usar el tipo `any`
Cuando declara una variable como `any`, el verificador de tipos de TypeScript no realiza ninguna comprobación de tipos, lo que hace posible asignar cualquier tipo de valores a la variable. TypeScript utiliza la inferencia de tipos para determinar el tipo de la variable a partir del valor. Al declararlo como `any`, TypeScript ya no puede inferir el tipo. Esto dificulta la captura de errores relacionados con el tipo durante el desarrollo, lo que lleva a errores en tiempo de ejecución y hace que el código sea menos mantenible, menos fiable y más difícil de entender para otros.
Por eso todo debe tener un tipo. Entonces, si crea un nuevo objeto con un nombre y apellido, debe crear una interfaz o tipo que contenga un nombre y apellido y defina la forma del objeto que está manipulando.

View File

@@ -0,0 +1,39 @@
---
title: Objetos personalizados
---
Los objetos son estructuras que te permiten almacenar datos (registros, atributos y valores) específicos de una organización. Twenty proporciona tanto objetos estándar como personalizados.
Los objetos estándar son objetos incorporados con un conjunto de atributos disponibles para todos los usuarios. Ejemplos de objetos estándar en Twenty incluyen Empresa y Persona. Los objetos estándar tienen campos estándar que también están disponibles para todos los usuarios de Twenty, como Company.displayName.
Los objetos personalizados son objetos que puedes crear para almacenar información que es única para tu organización. No están incorporados; los miembros de tu espacio de trabajo pueden crear y personalizar objetos personalizados para albergar información para la cual los objetos estándar no son aptos.
## Esquema de alto nivel
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Esquema de alto nivel" />
</div>
<br />
## Cómo funciona
Los objetos personalizados provienen de tablas de metadatos que determinan la forma, el nombre y el tipo de los objetos. Toda esta información está presente en la base de datos del esquema de metadatos, que consta de tablas:
* **DataSource**: Detalles de dónde se encuentra la información.
* **Object**: Describe el objeto y lo vincula a un DataSource.
* **Field**: Describe los campos de un objeto y lo conecta al objeto.
Para añadir un objeto personalizado, el workspaceMember consultará la API de /metadata. Esto actualiza los metadatos de acuerdo y calcula un esquema GraphQL basado en los metadatos, almacenándolo en un caché de GQL para su uso posterior.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Consultando la API de /metadata para añadir objetos personalizados" />
</div>
<br />
Para obtener datos, el proceso implica hacer consultas a través del endpoint /graphql y pasarlos a través del Query Resolver.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Consulta el endpoint /graphql para obtener datos" />
</div>

View File

@@ -0,0 +1,46 @@
---
title: Indicadores de característica
---
Los indicadores de característica se usan para ocultar características experimentales. Para Twenty, se configuran a nivel de espacio de trabajo y no a nivel de usuario.
## Agregar un nuevo indicador de característica
En `FeatureFlagKey.ts` agrega el indicador de característica:
```ts
type FeatureFlagKey =
| 'IS_FEATURENAME_ENABLED'
| ...;
```
También agrégalo al enum en `feature-flag.entity.ts`:
```ts
enum FeatureFlagKeys {
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
...
}
```
Para aplicar un indicador de característica en una característica de **backend** usa:
```ts
@Gate({
featureFlag: 'IS_FEATURENAME_ENABLED',
})
```
Para aplicar un indicador de característica en una característica de **frontend** usa:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## Configurar indicadores de característica para el despliegue
Cambie el registro correspondiente en la Tabla `core.featureFlag`:
| iD | clave | workspaceId | valor |
| --------- | ------------------------ | ------------------------- | ----------- |
| Aleatorio | `IS_FEATURENAME_ENABLED` | ID del espacio de trabajo | `verdadero` |

View File

@@ -0,0 +1,125 @@
---
title: Arquitectura de Carpetas
info: Una mirada detallada a la arquitectura de carpetas de nuestro servidor
---
La estructura del directorio backend es la siguiente:
```
server
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils
```
## Habilidad
Define permisos e incluye gestores para cada entidad.
## Decoradores
Define decoradores personalizados en NestJS para funcionalidad adicional.
Ver [decoradores personalizados](https://docs.nestjs.com/custom-decorators) para más detalles.
## Filtros
Incluye filtros de excepciones para manejar excepciones que puedan ocurrir en endpoints de GraphQL.
## Guardias
Ver [guardias](https://docs.nestjs.com/guards) para más detalles.
## Salud
Incluye una API REST públicamente disponible (healthz) que devuelve un JSON para confirmar si la base de datos está funcionando como se esperaba.
## Metadatos
Define objetos personalizados y hace disponible una API de GraphQL (graphql/metadata).
## Espacio de trabajo
Genera y sirve un esquema GraphQL personalizado basado en los metadatos.
### Estructura del Directorio de Espacio de Trabajo
```
workspace
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───workspace-query-builder
└───factories
└───interfaces
└───workspace-query-runner
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
```
La raíz del directorio de espacio de trabajo incluye el `espacio.trabajo.factory.ts`, un archivo que contiene la función `createGraphQLSchema`. Esta función genera un esquema específico para el espacio de trabajo utilizando los metadatos para adaptar un esquema para espacios de trabajo individuales. Al separar la construcción del esquema y del resolver, usamos la función `makeExecutableSchema`, que combina estos elementos discretos.
Esta estrategia no solo se trata de organización, sino que también ayuda con la optimización, como el almacenamiento en caché de definiciones de tipos generados para mejorar el rendimiento y la escalabilidad.
### Constructor de Esquema de Espacio de Trabajo
Genera el esquema GraphQL, e incluye:
#### Fábricas:
Constructores especializados para generar constructos relacionados con GraphQL.
* La fábrica de tipos traduce los metadatos de los campos en tipos GraphQL utilizando `TypeMapperService`.
* La fábrica de definiciones de tipos crea objetos de entrada o salida de GraphQL derivados de `objectMetadata`.
#### Tipos GraphQL
Incluye enumeraciones, entradas, objetos y escalares, y sirve como bloques de construcción para la construcción del esquema.
#### Interfaces y Definiciones de Objetos
Contiene los planos para entidades GraphQL, e incluye tanto tipos predefinidos como personalizados como `MONEY` o `URL`.
#### Servicios
Contiene el servicio responsable de asociar FieldMetadataType con su escalar de GraphQL apropiado o modificadores de consulta.
#### Almacenamiento
Incluye la clase `TypeDefinitionsStorage` que contiene definiciones de tipos reutilizables, previniendo duplicación de tipos GraphQL.
### Constructor de Resolver de Espacio de Trabajo
Crea funciones de resolutor para consultar y modificar el esquema GraphQL.
Cada fábrica en este directorio es responsable de producir un tipo de resolutor distinto, como el `FindManyResolverFactory`, diseñado para aplicación adaptable a través de varias tablas.
### Ejecutor de Consultas de Espacio de Trabajo
Ejecuta las consultas generadas en la base de datos y analiza el resultado.

View File

@@ -0,0 +1,41 @@
---
title: Cola de Mensajes
---
Las colas facilitan la realización de operaciones asíncronas. Se pueden utilizar para realizar tareas en segundo plano, como enviar un correo de bienvenida al registrarse.
Cada caso de uso tendrá su propia clase de cola extendida de `MessageQueueServiceBase`.
Actualmente, solo soportamos `bull-mq`[bull-mq](https://bullmq.io/) como el controlador de cola.
## Pasos para crear y usar una nueva cola
1. Agregue un nombre de cola para su nueva cola en la enumeración `MESSAGE_QUEUES`.
2. Proporcione la implementación de fábrica de la cola con el nombre de la cola como el token de dependencia.
3. Inyecte la cola que creó en el módulo/servicio requerido con el nombre de la cola como el token de dependencia.
4. Agregue una clase de trabajador con inyección basada en token, al igual que el productor.
### Ejemplo de uso
```ts
class Resolver {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
async onSomeAction() {
//business logic
await this.queue.add(someData);
}
}
//async worker
class CustomWorker {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
this.initWorker();
}
async initWorker() {
await this.queue.work(async ({ id, data }) => {
//worker logic
});
}
}
```

View File

@@ -0,0 +1,87 @@
---
title: Comandos de Backend
icon: terminal
---
## Comandos útiles
Estos comandos deben ejecutarse desde la carpeta packages/twenty-server.
Desde cualquier otra carpeta, puedes ejecutar `npx nx {command} twenty-server` (o `npx nx run twenty-server:{command}`).
### Configuración inicial
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### Iniciando el servidor
```
npx nx run twenty-server:start
```
### Lint
```
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Prueba
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Nota: puedes ejecutar `npx nx run twenty-server:test:integration:with-db-reset` en caso de que necesites restablecer la base de datos antes de ejecutar las pruebas de integración.
### Restablecer la base de datos
Si deseas restablecer y sembrar la base de datos, puedes ejecutar el siguiente comando:
```bash
npx nx run twenty-server:database:reset
```
### Migraciones
#### Para objetos en esquemas Core/Metadata (TypeORM)
```bash
npx nx run twenty-server:database:migrate:generate
```
## Stack Tecnológico
Twenty utiliza principalmente NestJS para el backend.
Prisma fue el primer ORM que usamos. Pero para permitir a los usuarios crear campos y objetos personalizados, un nivel más bajo tenía más sentido ya que necesitamos tener un control detallado. El proyecto ahora usa TypeORM.
Así es como se ve la pila tecnológica ahora.
**Core**
* [NestJS](https://nestjs.com/)
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**Base de datos**
* [Postgres](https://www.postgresql.org/)
**Integraciones de terceros**
* [Sentry](https://sentry.io/welcome/) para rastrear errores
**Pruebas**
* [Jest](https://jestjs.io/)
**Herramientas**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
**Desarrollo**
* [AWS EKS](https://aws.amazon.com/eks/)

View File

@@ -0,0 +1,85 @@
---
title: Aplicación Zapier
---
Sincroniza sin esfuerzo Twenty con más de 3000 aplicaciones usando [Zapier](https://zapier.com/). ¡Automatiza tareas, mejora la productividad y potencia tus relaciones con los clientes!
## Acerca de Zapier
Zapier es una herramienta que permite automatizar flujos de trabajo al conectar las aplicaciones que tu equipo utiliza a diario. El concepto fundamental de Zapier son los flujos de trabajo automatizados, llamados Zaps, que incluyen desencadenantes y acciones.
Puedes aprender más sobre cómo funciona Zapier [aquí](https://zapier.com/how-it-works).
## Configuración
### Paso 1: Instalar paquetes de Zapier
```bash
cd packages/twenty-zapier
yarn
```
### Paso 2: Iniciar sesión con la CLI
Utiliza tus credenciales de Zapier para iniciar sesión usando la CLI:
```bash
zapier login
```
### Paso 3: Configurar variables de entorno
Desde la carpeta `packages/twenty-zapier`, ejecuta:
```bash
cp .env.example .env
```
Ejecuta la aplicación localmente, ve a [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks) y genera una clave API.
Reemplaza el valor de **YOUR_API_KEY** en el archivo `.env` con la clave API que acabas de generar.
## Desarrollo
<Warning>
Asegúrate de ejecutar `yarn build` antes de cualquier comando `zapier`.
</Warning>
### Prueba
```bash
yarn test
```
### Lint
```bash
yarn format
```
### Observa y compila mientras editas el código
```bash
yarn watch
```
### Valida tu aplicación Zapier
```bash
yarn validate
```
### Despliega tu aplicación Zapier
```bash
yarn deploy
```
### Lista todos los comandos de Zapier CLI
```bash
zapier
```

View File

@@ -0,0 +1,79 @@
---
title: Errores, solicitudes y pull requests
icon: bug
info: Informa de problemas, solicita funcionalidades y contribuye con código
---
## Reportar errores
Para reportar un error, por favor [crea un problema en GitHub](https://github.com/twentyhq/twenty/issues/new).
También puedes pedir ayuda en [Discord](https://discord.gg/cx5n4Jzs57).
## Solicitudes de funciones
Si no estás seguro de si es un error y sientes que está más cerca de una solicitud de función, entonces probablemente deberías [abrir una discusión en su lugar](https://github.com/twentyhq/twenty/discussions/new).
## Envía un pull request
Contribuir código a Twenty comienza con un pull request (PR).
### Antes de empezar
1. Consulta [los issues existentes](https://github.com/twentyhq/twenty/issues) para ver trabajos relacionados
2. Para nuevas funcionalidades, abre primero un issue para discutir
3. Revisa nuestro [Código de conducta](https://github.com/twentyhq/twenty/blob/main/.github/CODE_OF_CONDUCT.md)
### Haz un fork y clona
1. Haz un fork del repositorio en GitHub
2. Clona tu fork:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. Añade el remoto upstream:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### Crea una rama
```bash
git checkout -b feature/your-feature-name
```
Usa nombres de rama descriptivos:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### Realiza tus cambios
1. Escribe código limpio y bien documentado
2. Sigue el estilo de código existente
3. Añade pruebas para la nueva funcionalidad
4. Actualiza la documentación si es necesario
### Envía tu PR
1. Haz push de tu rama:
```bash
git push origin feature/your-feature-name
```
2. Abre un PR en GitHub
3. Rellena la plantilla del PR
4. Vincula los issues relacionados
### Lista de verificación de PR
* [ ] El código sigue las pautas de estilo del proyecto
* [ ] Las pruebas pasan localmente
* [ ] La documentación está actualizada
* [ ] La descripción del PR explica los cambios

View File

@@ -0,0 +1,329 @@
---
title: Mejores prácticas
icon: star
---
Este documento describe las mejores prácticas que debes seguir al trabajar en el frontend.
## Gestión de estado
React y Jotai manejan la gestión de estado en la base de código.
### Usa átomos de Jotai para almacenar el estado
Es buena práctica crear tantos átomos como necesites para almacenar tu estado.
<Warning>
Es mejor usar átomos adicionales que intentar ser demasiado concisos con la perforación de props.
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### No uses `useRef` para almacenar el estado
Evita usar `useRef` para almacenar el estado.
Si deseas almacenar el estado, deberías usar `useState` o átomos de Jotai con `useAtomState`.
Consulta [cómo gestionar las re-renderizaciones](#managing-re-renders) si sientes que necesitas `useRef` para evitar algunas re-renderizaciones.
## Gestión de las re-renderizaciones
Las re-renderizaciones pueden ser difíciles de gestionar en React.
Aquí hay algunas reglas a seguir para evitar re-renderizaciones innecesarias.
Ten en cuenta que siempre puedes evitar re-renderizaciones comprendiendo su causa.
### Trabaja a nivel de raíz
Ahora es fácil evitar re-renderizaciones en nuevas funciones eliminándolas a nivel de raíz.
El componente sidecar `PageChangeEffect` contiene sólo un `useEffect` que contiene toda la lógica a ejecutar en un cambio de página.
De esa manera, sabes que solo hay un lugar que puede desencadenar una re-renderización.
### Siempre piensa dos veces antes de añadir `useEffect` en tu base de código
Re-renders son a menudo causados por `useEffect` innecesario.
Debería pensar si necesita `useEffect`, o si puede mover la lógica en una función manejadora de eventos.
Generalmente te resultará fácil mover la lógica en una función `handleClick` o `handleChange`.
También puedes encontrarlas en bibliotecas como Apollo: `onCompleted`, `onError`, etc.
### Usa un componente hermano para extraer `useEffect` o lógica de obtención de datos
Si crees que necesitas añadir un `useEffect` en tu componente raíz, deberías considerar extraerlo en un componente sidecar.
Puedes aplicar lo mismo para la lógica de obtención de datos, con hooks de Apollo.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### Usa familias de átomos y familias de selectores
Las familias de átomos y las familias de selectores son una gran manera de evitar re-renderizados.
Son útiles cuando necesitas almacenar una lista de elementos.
### No deberías usar `React.memo(MyComponent)`
Evita usar `React.memo()` porque no resuelve la causa de la re-renderización, sino que rompe la cadena de re-renderización, lo que puede llevar a un comportamiento inesperado y hacer que el código sea muy difícil de refactorizar.
### Limita el uso de `useCallback` o `useMemo`
A menudo no son necesarios y harán que el código sea más difícil de leer y mantener para una ganancia de rendimiento que es imperceptible.
## Console.logs
Las declaraciones `console.log` son valiosas durante el desarrollo, ofreciendo información en tiempo real sobre los valores de las variables y el flujo de código. Pero, dejarlas en el código de producción puede llevar a varios problemas:
1. **Rendimiento**: El registro excesivo puede afectar el rendimiento en tiempo de ejecución, especialmente en aplicaciones del lado del cliente.
2. **Seguridad**: Registrar datos sensibles puede exponer información crítica a cualquier persona que inspeccione la consola del navegador.
3. **Limpieza**: Llenar la consola con registros puede oscurecer advertencias o errores importantes que los desarrolladores o herramientas necesitan ver.
4. **Profesionalismo**: Los usuarios finales o clientes que revisen la consola y vean una miríada de declaraciones de registros podrían cuestionar la calidad y el acabado del código.
Asegúrate de eliminar todos los `console.logs` antes de enviar el código a producción.
## Nomenclatura
### Nombres de variables
Los nombres de variables deben describir con precisión el propósito o función de la variable.
#### El problema con los nombres genéricos
Los nombres genéricos en programación no son ideales porque carecen de especificidad, lo que lleva a la ambigüedad y reduce la legibilidad del código. Tales nombres no transmiten el propósito de la variable o función, lo que dificulta a los desarrolladores entender la intención del código sin una investigación más profunda. Esto puede resultar en un aumento del tiempo de depuración, una mayor susceptibilidad a errores y dificultades en el mantenimiento y colaboración. Mientras tanto, la nomenclatura descriptiva hace que el código sea autoexplicativo y más fácil de navegar, mejorando la calidad del código y la productividad del desarrollador.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### Algunas palabras a evitar en los nombres de variables
* ficticio
### Manejadores de eventos
Los nombres de manejadores de eventos deben comenzar con `handle`, mientras que `on` es un prefijo usado para nombrar eventos en las props de los componentes.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Props opcionales
Evita pasar el valor predeterminado para una prop opcional.
**EJEMPLO**
Toma el componente `EmailField` definido a continuación:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Uso**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## Componente como props
Intenta tanto como sea posible pasar componentes no instanciados como propiedades, para que los hijos puedan decidir por sí mismos qué propiedades necesitan pasar.
El ejemplo más común de esto son los componentes de icono:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
Para que React entienda que el componente es un componente, necesitas usar PascalCase, para luego instanciarlo con `<MyIcon>`
## Prop Drilling: Mantenlo Minimalista
El prop drilling, en el contexto de React, se refiere a la práctica de pasar variables de estado y sus setters a través de muchas capas de componentes, incluso si los componentes intermedios no los usan. Aunque a veces es necesario, el exceso de prop drilling puede llevar a:
1. **Readabilidad Reducida**: Rastrear de dónde proviene una propiedad o dónde se utiliza puede volverse complicado en una estructura de componentes muy anidada.
2. **Desafíos de Mantenimiento**: Los cambios en la estructura de propiedades de un componente podrían requerir ajustes en varios componentes, incluso si no utilizan directamente la propiedad.
3. **Reducción de la Reusabilidad del Componente**: Un componente que recibe muchas propiedades solo para pasarlas se vuelve menos general y más difícil de reutilizar en diferentes contextos.
Si sientes que estás usando en exceso el prop drilling, consulta [mejores prácticas de gestión de estado](#state-management).
## Importar
Al importar, opta por los alias designados en lugar de especificar rutas completas o relativas.
**Alias del identificador**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Uso**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## Validación de Esquema
[Zod](https://github.com/colinhacks/zod) es el validador de esquemas para objetos no tipados:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Cambios Cruciales
Siempre ejecuta pruebas manuales exhaustivas antes de proceder para garantizar que las modificaciones no hayan causado interrupciones en otras partes, dado que las pruebas aún no se han integrado extensivamente.

View File

@@ -0,0 +1,110 @@
---
title: Arquitectura de Carpetas
icon: folder-tree
info: Una mirada detallada a nuestra arquitectura de carpetas
---
En esta guía, explorarás los detalles de la estructura del directorio del proyecto y cómo contribuye a la organización y mantenibilidad de Twenty.
Siguiendo esta convención de arquitectura de carpetas, es más fácil encontrar los archivos relacionados con funciones específicas y asegurar que la aplicación sea escalable y mantenible.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Páginas
Incluye los componentes de alto nivel definidos por las rutas de la aplicación. Importan más componentes de bajo nivel de la carpeta de módulos (más detalles abajo).
## Módulos
Cada módulo representa una función o un grupo de funciones, comprendiendo sus componentes específicos, estados y lógica operativa.
Todos deberían seguir la estructura siguiente. Puedes anidar módulos dentro de módulos (denominados submódulos) y se aplicarán las mismas reglas.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Contextos
Un contexto es una manera de pasar datos a través del árbol de componentes sin tener que pasar propiedades manualmente en cada nivel.
Ver [React Context](https://react.dev/reference/react#context-hooks) para más detalles.
### GraphQL
Incluye fragmentos, consultas y mutaciones.
Ver [GraphQL](https://graphql.org/learn/) para más detalles.
* Fragmentos
Un fragmento es una parte reutilizable de una consulta, que puedes usar en diferentes lugares. Usando fragmentos, es más fácil evitar la duplicación de código.
Ver [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) para más detalles.
* Consultas
Ver [GraphQL Queries](https://graphql.org/learn/queries/) para más detalles.
* Mutaciones
Ver [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) para más detalles.
### Hooks
Ver [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) para más detalles.
### Estados
Contiene la lógica de gestión de estado. [Jotai](https://jotai.org) maneja esto.
* Selectores: Los átomos derivados (usando `createAtomSelector`) calculan valores a partir de otros átomos y se memoizan automáticamente.
La gestión de estado incorporada de React todavía maneja el estado dentro de un componente.
### Utilidades
Debería contener solo funciones puras reutilizables. De lo contrario, crea hooks personalizados en la carpeta `hooks`.
## Interfaz de usuario
Contiene todos los componentes de interfaz de usuario reutilizables utilizados en la aplicación.
Esta carpeta puede contener subcarpetas, como `datos`, `visualización`, `retroalimentación` y `entrada` para tipos específicos de componentes. Cada componente debe ser autónomo y reutilizable, para que puedas usarlo en diferentes partes de la aplicación.
Al separar los componentes de la interfaz de usuario de otros componentes en la carpeta `modules`, es más fácil mantener un diseño consistente y realizar cambios en la interfaz de usuario sin afectar otras partes (lógica de negocio) del código base.
## Interfaz y dependencias
Puedes importar otro código de módulo desde cualquier módulo excepto desde la carpeta `ui`. Esto mantendrá su código fácil de probar.
### Interno
Cada parte (hooks, estados, ...) de un módulo puede tener una carpeta `internal`, que contiene partes que se usan solo dentro del módulo.

View File

@@ -0,0 +1,91 @@
---
title: Comandos del Frontend
icon: terminal
---
## Comandos útiles
### Iniciando la aplicación
```bash
npx nx start twenty-front
```
### Regenerar el esquema de graphql basado en el esquema API graphql
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
O
```bash
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Traducciones
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Prueba
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## Stack Tecnológico
El proyecto tiene un stack limpio y sencillo, con un código boilerplate mínimo.
**Aplicación**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**Pruebas**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Herramientas**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Arquitectura
### Enrutamiento
[React Router](https://reactrouter.com/) maneja el enrutamiento.
Para evitar [re-renders]innecesarios (/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) toda la lógica de enrutamiento está en un `useEffect` en `PageChangeEffect`.
### Gestión del Estado
[Jotai](https://jotai.org/) gestiona el estado.
Ver [mejores prácticas](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) para más información sobre la gestión del estado.
## Pruebas
[Jest](https://jestjs.io/) sirve como la herramienta para pruebas unitarias mientras [Storybook](https://storybook.js.org/) es para pruebas de componentes.
Jest es principalmente para probar funciones utilitarias, y no los componentes en sí mismos.
Storybook es para probar el comportamiento de componentes aislados, así como mostrar el sistema de diseño.

View File

@@ -0,0 +1,178 @@
---
title: Atajos de teclado
---
## Introducción
Cuando necesitas escuchar una tecla de acceso rápido, normalmente usarías el evento `onKeyDown`.
En `twenty-front`, sin embargo, podrías tener conflictos entre los mismos atajos de teclado utilizados en diferentes componentes, montados al mismo tiempo.
Por ejemplo, si tienes una página que escucha la tecla Enter y un modal que escucha la tecla Enter, con un componente Select dentro de ese modal que también escucha la tecla Enter, podrías tener un conflicto cuando todos están montados al mismo tiempo.
## El gancho `useScopedHotkeys`
Para manejar este problema, tenemos un gancho personalizado que hace posible escuchar atajos de teclado sin ningún conflicto.
Lo colocas en un componente, y escuchará los atajos de teclado solo cuando el componente está montado Y cuando el **ámbito del atajo de teclado** especificado está activo.
## ¿Cómo escuchar atajos de teclado en la práctica?
Hay dos pasos involucrados en configurar la escucha de atajos de teclado:
1. Establece el [ámbito del atajo de teclado](#qué-es-un-ambito-de-atajo-de-teclado-) que escuchará los atajos de teclado
2. Usa el gancho `useScopedHotkeys` para escuchar atajos de teclado
Configurar los ámbitos de atajos de teclado es necesario incluso en páginas simples, porque otros elementos de la interfaz de usuario como el menú lateral o el menú de comandos también podrían escuchar atajos de teclado.
## Casos de uso para atajos de teclado
En general, tendrás dos casos de uso que requieren atajos de teclado:
1. En una página o un componente montado en una página
2. En un componente tipo modal que toma el enfoque debido a la acción del usuario
El segundo caso de uso puede ocurrir recursivamente: un desplegable en un modal, por ejemplo.
### Escuchando atajos de teclado en una página
Ejemplo:
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### Escuchando atajos de teclado en un componente tipo modal
Para este ejemplo utilizaremos un componente modal que escucha la tecla Escape para indicar a su padre que lo cierre.
Aquí la interacción del usuario está cambiando el ámbito.
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
Luego, en el componente modal:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
Es importante usar este patrón cuando no está seguro de que sólo usar useEffect con montaje/desmontaje sea suficiente para evitar conflictos.
Esos conflictos pueden ser difíciles de depurar y podría ocurrir con más frecuencia que con useEffects.
## ¿Qué es un ámbito de atajo de teclado?
Un ámbito de atajo de teclado es una cadena que representa un contexto en el que los atajos de teclado están activos. Por lo general, se codifica como un enum.
Cuando cambias el ámbito del atajo de teclado, se habilitarán los atajos que están escuchando este ámbito y se deshabilitarán los atajos que escuchan otros ámbitos.
Solo puedes establecer un ámbito a la vez.
Como ejemplo, los ámbitos de atajos de teclado para cada página se definen en el enum `PageHotkeyScope`:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
Internamente, el ámbito seleccionado actualmente se almacena en un átomo de Jotai que se comparte en toda la aplicación:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
¡Pero este átomo nunca debe manejarse manualmente! Veremos cómo usarlo en la siguiente sección.
## ¿Cómo funciona internamente?
Hicimos un contenedor delgado sobre [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) que lo hace más eficiente y evita renders innecesarios.
También creamos un átomo de Jotai para manejar el estado del ámbito de atajos de teclado y hacerlo disponible en toda la aplicación.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Navegar por la biblioteca de componentes de interfaz de Twenty
---
Ver nuestra biblioteca completa de componentes y documentación en Storybook.
[Abrir Storybook →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Guía de Estilo
icon: paintbrush
---
Este documento incluye las reglas a seguir al escribir código.
El objetivo aquí es tener una base de código coherente, que sea fácil de leer y de mantener.
Para esto, es mejor ser un poco más detallado que ser demasiado conciso.
Ten siempre en cuenta que la gente lee código más a menudo de lo que lo escribe, especialmente en un proyecto de código abierto, donde cualquiera puede contribuir.
Hay muchas reglas que no están definidas aquí, pero que son verificadas automáticamente por linters.
## React
### Usar componentes funcionales
Siempre usa componentes funcionales TSX.
No uses `import` por defecto con `const`, porque es más difícil de leer y más difícil de importar con el autocompletado de código.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### "Props"
Crea el tipo de las props y llámalo `(NombreDelComponente)Props` si no hay necesidad de exportarlo.
Usa la desestructuración de props.
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### Evita usar `React.FC` o `React.FunctionComponent` para definir tipos de props
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### Sin Propagación de una sola variable de Props en Elementos JSX
Evita usar la propagación de una sola variable de props en elementos JSX, como `{...props}`. Esta práctica a menudo resulta en un código que es menos legible y más difícil de mantener porque no está claro qué props está recibiendo el componente.
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
Razonamiento:
* A simple vista, es más claro qué props se está pasando, lo que hace que sea más fácil de entender y mantener.
* Ayuda a prevenir el acoplamiento estricto entre componentes mediante sus props.
* Las herramientas de linting facilitan la identificación de props mal escritas o sin uso al listar props explícitamente.
## JavaScript
### Usar el operador de fusión nula `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Usar encadenamiento opcional `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Usar `type` en lugar de `interface`
Siempre usa `type` en lugar de `interface`, porque casi siempre se superponen y `type` es más flexible.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### Usar literales de cadena en lugar de enums
[Los literales de cadena](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) son la manera preferida para manejar valores tipo enum en TypeScript. Son más fáciles de extender con Pick y Omit, y ofrecen una mejor experiencia de desarrollo, especialmente con la autocompletación de código.
Puedes ver por qué TypeScript recomienda evitar enums [aquí](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL y bibliotecas internas
Deberías usar enums que genera el codegen de GraphQL.
También es mejor usar un enum al usar una biblioteca interna, para que la biblioteca interna no tenga que exponer un tipo de literal de cadena que no está relacionado con la API interna.
Ejemplo:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Estilo
### Usar StyledComponents
Estiliza los componentes con [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Prefija los componentes estilizados con "Styled" para diferenciarlos de los componentes "reales".
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Tematización
Utilizar el tema para la mayor parte de la estilización de los componentes es el enfoque preferido.
#### Unidades de medida
Evita usar valores `px` o `rem` directamente dentro de los componentes estilizados. Los valores necesarios suelen estar ya definidos en el tema, por lo que se recomienda usar el tema para estos fines.
#### Colores
Abstente de introducir nuevos colores; en su lugar, utiliza la paleta existente del tema. Si hay una situación en la que la paleta no se ajusta, deja un comentario para que el equipo pueda corregirlo.
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## Aplicando No-Type Imports
Evita las importaciones de tipo. Para reforzar este estándar, una regla de Oxlint verifica y reporta cualquier importación de tipo. Esto ayuda a mantener la consistencia y la legibilidad en el código TypeScript.
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### Por qué No-Type Imports
* **Consistencia**: Al evitar las importaciones de tipo y usar un solo enfoque tanto para las importaciones de tipo como de valor, la base de código se mantiene consistente en su estilo de importación de módulos.
* **Legibilidad**: Las no-importaciones de tipo mejoran la legibilidad del código al dejar claro cuándo se están importando valores o tipos. Esto reduce la ambigüedad y hace más fácil entender el propósito de los símbolos importados.
* **Mantenibilidad**: Mejora la mantenibilidad de la base de código porque los desarrolladores pueden identificar y localizar importaciones solo de tipo al revisar o modificar el código.
### Regla de Oxlint
Una regla de Oxlint, `typescript/consistent-type-imports`, impone el estándar de no usar "type" en las importaciones. Esta regla generará errores o advertencias sobre cualquier violación de importación de tipo.
Por favor, ten en cuenta que esta regla específicamente aborda extraños casos límite donde ocurren importaciones de tipo no intencionadas. TypeScript en sí mismo desaconseja esta práctica, como se menciona en las [notas de lanzamiento de TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). En la mayoría de situaciones, no deberías necesitar usar importaciones solo de tipo.
Para asegurarte de que tu código cumpla con esta regla, asegúrate de ejecutar Oxlint como parte de tu flujo de trabajo de desarrollo.

View File

@@ -0,0 +1,61 @@
---
title: Trabajar con Figma
info: Aprende cómo puedes colaborar con Twenty en Figma
---
Figma es una herramienta de diseño de interfaces colaborativa que ayuda a superar la barrera de comunicación entre diseñadores y desarrolladores.
Esta guía explica cómo puedes colaborar con Figma.
## Acceso
1. **Accede al enlace compartido:** Puedes acceder al archivo de Figma del proyecto [aquí](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Inicia sesión:** Si aún no has iniciado sesión, Figma te pedirá que lo hagas.
Las características clave solo están disponibles para usuarios registrados, como el modo desarrollador y la capacidad de seleccionar un marco dedicado.
<Warning>
No podrás colaborar efectivamente sin una cuenta.
</Warning>
## Estructura de Figma
En la barra lateral izquierda, puedes acceder a las diferentes páginas del Figma de Twenty. Así es como están organizadas:
* **Página de componentes:** Esta es la primera página. El diseñador la utiliza para crear y organizar los elementos de diseño reutilizables que se usan en todo el archivo de diseño. Por ejemplo, botones, íconos, símbolos u otros componentes reutilizables. Sirve para mantener la consistencia a lo largo del diseño.
* **Página principal:** La segunda página es la página principal, que muestra la interfaz de usuario completa del proyecto. Puedes presionar ***Play*** para usar el prototipo completo de la aplicación.
* **Páginas de características:** Las otras páginas están típicamente dedicadas a características en progreso. Contienen el diseño de características específicas o módulos de la aplicación o sitio web. Normalmente, aún están en desarrollo.
## Consejos útiles
Con acceso de solo lectura, no puedes editar el diseño, pero puedes acceder a todas las características que serán útiles para convertir los diseños en código.
### Usa el modo Dev
El Modo Dev de Figma mejora la productividad de los desarrolladores al proporcionar una navegación fácil por el diseño, una gestión eficaz de assets, herramientas de comunicación eficientes, integraciones de caja de herramientas, fragmentos de código rápidos e información clave de capas, superando la brecha entre diseño y desarrollo. Puedes aprender más sobre el Modo Dev [aquí](https://www.figma.com/dev-mode/).
Cambia al modo "Desarrollador" en la parte derecha de la barra de herramientas para ver las especificaciones de diseño, copiar CSS y acceder a los assets.
### Usar el prototipo
Haz clic en cualquier elemento del lienzo y presiona el botón "Play" en el extremo superior derecho de la interfaz para acceder a la vista del prototipo. El modo de prototipo te permite interactuar con el diseño como si fuera el producto final. Demuestra el flujo entre pantallas y cómo los elementos de la interfaz, como botones, enlaces o menús, se comportan al interactuar con ellos.
1. **Entendiendo las transiciones y animaciones:** En el modo Prototipo, puedes ver cualquier transición o animación añadida por un diseñador entre pantallas o elementos de UI, proporcionando instrucciones visuales claras a los desarrolladores sobre la conducta y el estilo intencionados.
2. **Aclaración de implementación:** Un prototipo también puede ayudar a reducir las ambigüedades. Los desarrolladores pueden interactuar con él para obtener una mejor comprensión de la funcionalidad o apariencia de elementos particulares.
Para más detalles y orientación sobre el aprendizaje de la plataforma Figma, puedes visitar la [Documentación Oficial de Figma](https://help.figma.com/hc/en-us).
### Medir distancias
Selecciona un elemento, mantén presionada la tecla `Option` (Mac) o `Alt` (Windows), luego pasa el cursor sobre otro elemento para ver la distancia entre ellos.
### Extensión Figma para VSCode (Recomendado)
[Figma para VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
te permite navegar e inspeccionar archivos de diseño, colaborar con diseñadores, rastrear cambios y acelerar la implementación, todo sin salir de tu editor de texto.
Forma parte de nuestras extensiones recomendadas.
## Colaboración
1. **Uso de Comentarios:** Puedes utilizar la función de comentarios haciendo clic en el ícono de burbuja en la parte izquierda de la barra de herramientas.
2. **Chat de Cursor:** Una característica agradable de Figma es el Chat de Cursor. Simplemente presiona `;` en Mac y `/` en Windows para enviar un mensaje si ves a alguien más usando Figma al mismo tiempo que tú.

View File

@@ -0,0 +1,318 @@
---
title: Configuración Local
icon: laptop-code
description: La guía para los colaboradores (o desarrolladores curiosos) que quieren ejecutar Twenty localmente.
---
## Prerrequisitos
<Tabs>
<Tab title="Linux y macOS">
Antes de que puedas instalar y usar Twenty, asegúrate de instalar lo siguiente en tu computadora:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
`npm` no funcionará, deberías usar `yarn` en su lugar. Yarn ahora se envía con Node.js, por lo que no necesitas instalarlo por separado.
Solo tienes que ejecutar `corepack enable` para habilitar Yarn si aún no lo has hecho.
</Warning>
</Tab>
<Tab title="Windows (WSL)">
1. Instalar WSL
Abre PowerShell como Administrador y ejecuta:
```powershell
wsl --install
```
Ahora deberías ver un aviso para reiniciar tu computadora. Si no, reiníciala manualmente.
Al reiniciar, se abrirá una ventana de PowerShell e instalará Ubuntu. Esto puede tomar algo de tiempo.
Verás un aviso para crear un nombre de usuario y contraseña para tu instalación de Ubuntu.
2. Instalar y configurar git
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
3. Instalar nvm, node.js y yarn
<Warning>
Usa `nvm` para instalar la versión correcta de `node`. El `.nvmrc` asegura que todos los colaboradores usen la misma versión.
</Warning>
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
Cierra y vuelve a abrir tu terminal para usar nvm. Luego ejecuta los siguientes comandos.
```bash
nvm install # installs recommended node version
nvm use # use recommended node version
corepack enable
```
</Tab>
</Tabs>
---
## Paso 1: Clonar con Git
En tu terminal, ejecuta el siguiente comando.
<Tabs>
<Tab title="SSH (Recomendado)">
Si aún no has configurado claves SSH, puedes aprender cómo hacerlo [aquí](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
<Tab title="HTTPS">
```bash
git clone https://github.com/twentyhq/twenty.git
```
</Tab>
</Tabs>
## Paso 2: Ubícate en la raíz
```bash
cd twenty
```
Debes ejecutar todos los comandos de los siguientes pasos desde la raíz del proyecto.
## Paso 3: Configurar una Base de Datos PostgreSQL
<Tabs>
<Tab title="Linux">
**Opción 1 (preferida):** Para aprovisionar tu base de datos localmente:
Usa el siguiente enlace para instalar PostgreSQL en tu máquina Linux: [Instalación de PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Nota: Puede que necesites agregar `sudo -u postgres` antes del comando `psql` para evitar errores de permisos.
**Opción 2:** Si tienes Docker instalado:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Mac OS">
**Opción 1 (preferido):** Para aprovisionar tu base de datos localmente con `brew`:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Puedes verificar si el servidor PostgreSQL está corriendo ejecutando:
```bash
brew services list
```
El instalador puede que no cree el usuario `postgres` por defecto al instalar
vía Homebrew en macOS. En cambio, crea un rol de PostgreSQL que coincide con tu
nombre de usuario de macOS (por ejemplo, "john").
Para comprobar y crear el usuario `postgres` si es necesario, sigue estos pasos:
```bash
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
Una vez en el comando psql (postgres=#), ejecuta:
```bash
# List existing PostgreSQL roles
\du
```
Verás una salida similar a:
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
Si no ves un rol `postgres` listado, procede al siguiente paso.
Crea el rol `postgres` manualmente:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
Esto crea un rol de superusuario llamado `postgres` con acceso de inicio de sesión.
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Opción 2:** Si tienes Docker instalado:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
Todos los siguientes pasos deben ejecutarse en la terminal de WSL (dentro de tu máquina virtual)
**Opción 1:** Para aprovisionar tu PostgreSQL localmente:
Usa el siguiente enlace para instalar PostgreSQL en tu máquina virtual Linux: [Instalación de PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Nota: Puede que necesites agregar `sudo -u postgres` antes del comando `psql` para evitar errores de permisos.
**Opción 2:** Si tienes Docker instalado:
Ejecutar Docker en WSL agrega una capa extra de complejidad.
Solo usa esta opción si estás cómodo con los pasos extras involucrados, incluyendo activar [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
Ahora puedes acceder a la base de datos en `localhost:5432`.
Si utilizaste la opción de Docker anterior, las credenciales predeterminadas son usuario `postgres` y contraseña `postgres`. Para instalaciones nativas de PostgreSQL, usa las credenciales y los roles configurados en tu máquina.
## Paso 4: Configurar una Base de Datos Redis (cache)
Twenty requiere un caché de Redis para proporcionar el mejor rendimiento.
<Tabs>
<Tab title="Linux">
**Opción 1:** Para aprovisionar tu Redis localmente:
Usa el siguiente enlace para instalar Redis en tu máquina Linux: [Instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Opción 2:** Si tienes Docker instalado:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
**Opción 1 (preferido):** Para aprovisionar tu Redis localmente con `brew`:
```bash
brew install redis
```
Inicia tu servidor de Redis:
```bash
brew services start redis
```
**Opción 2:** Si tienes Docker instalado:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
**Opción 1:** Para aprovisionar tu Redis localmente:
Usa el siguiente enlace para instalar Redis en tu máquina virtual Linux: [Instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Opción 2:** Si tienes Docker instalado:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
Si necesitas una interfaz gráfica de cliente, recomendamos [Redis Insight](https://redis.io/insight/) (versión gratuita disponible).
## Paso 5: Configurar las variables de entorno
Usa variables de entorno o archivos `.env` para configurar tu proyecto. Más información [aquí](/l/es/developers/self-host/capabilities/setup).
Copia los archivos `.env.example` en `/front` y `/server`:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**Modo de múltiples espacios de trabajo:** De forma predeterminada, Twenty se ejecuta en modo de un solo espacio de trabajo, donde solo se puede crear un espacio de trabajo. Para habilitar la compatibilidad con múltiples espacios de trabajo (útil para probar funciones basadas en subdominios), establece `IS_MULTIWORKSPACE_ENABLED=true` en el archivo `.env` de tu servidor. Consulta [Modo de múltiples espacios de trabajo](/l/es/developers/self-host/capabilities/setup#multi-workspace-mode) para más detalles.
</Info>
## Paso 6: Instalación de dependencias
Para compilar el servidor de Twenty e ingresar algunos datos en tu base de datos, ejecuta el siguiente comando:
```bash
yarn
```
Ten en cuenta que `npm` o `pnpm` no funcionarán
## Paso 7: Ejecutar el proyecto
<Tabs>
<Tab title="Linux">
Dependiendo de tu distribución de Linux, el servidor Redis podría iniciarse automáticamente.
Si no, revisa la [guía de instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) para tu distribución.
</Tab>
<Tab title="Mac OS">
Redis ya debería estar funcionando. Si no, ejecuta:
```bash
brew services start redis
```
</Tab>
<Tab title="Windows (WSL)">
Dependiendo de tu distribución de Linux, el servidor Redis podría iniciarse automáticamente.
Si no es así, consulte la [guía de instalación de Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) para su distribución.
</Tab>
</Tabs>
Configure su base de datos con el siguiente comando:
```bash
npx nx database:reset twenty-server
```
Inicie el servidor, el trabajador y los servicios frontend:
```bash
npx nx start twenty-server
npx nx worker twenty-server
npx nx start twenty-front
```
Alternativamente, puede iniciar todos los servicios a la vez:
```bash
npx nx start
```
## Paso 8: Use Twenty
**Frontend**
El frontend de Twenty estará ejecutándose en [http://localhost:3001](http://localhost:3001).
Puede iniciar sesión usando la cuenta demo por defecto: `tim@apple.dev` (contraseña: `tim@apple.dev`)
**Backend**
* El servidor de Twenty estará operativo en [http://localhost:3000](http://localhost:3000)
* La API GraphQL puede ser accedida en [http://localhost:3000/graphql](http://localhost:3000/graphql)
* La API REST puede ser alcanzada en [http://localhost:3000/rest](http://localhost:3000/rest)
## Solución de Problemas
Si encuentras algún problema, consulta [Solución de Problemas](/l/es/developers/self-host/capabilities/troubleshooting) para ver soluciones.