From 7478c02b004a3d96f168aeb67864d51fa2130ed6 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 14:05:25 +0100 Subject: [PATCH 01/34] Prepare the site for translation Wrap the homepage copy in Translate so it can be extracted, give generated adaptor sidebar items unique keys so a second locale can build, and declare the i18n config with English as the only locale. Nothing changes for readers. --- docusaurus.config.js | 16 ++++++ sidebars-adaptors.js | 15 ++++- src/pages/index.js | 130 +++++++++++++++++++++++++++++++------------ 3 files changed, 124 insertions(+), 37 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index fdf1a521f462..8fb30e9a972c 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -12,6 +12,19 @@ module.exports = { favicon: 'img/favicon.ico', organizationName: 'openfn', projectName: 'docs', + // English only for now. Translated content will live in i18n//, + // and anything not translated falls back to the English source. + i18n: { + defaultLocale: 'en', + locales: ['en'], + localeConfigs: { + en: { + label: 'English', + direction: 'ltr', + htmlLang: 'en', + }, + }, + }, markdown: { hooks: { onBrokenMarkdownLinks: 'warn' }, mermaid: true, @@ -151,6 +164,9 @@ module.exports = { sidebarPath: require.resolve('./sidebars-main.js'), routeBasePath: '/documentation', editUrl: 'https://github.com/openfn/docs/edit/main', + // Point "Edit this page" at the translated file rather than the + // English source when reading a non-default locale. + editLocalizedFiles: true, lastVersion: 'current', versions: { current: { diff --git a/sidebars-adaptors.js b/sidebars-adaptors.js index fe4b96418fd7..b94e3b8b5614 100644 --- a/sidebars-adaptors.js +++ b/sidebars-adaptors.js @@ -25,28 +25,38 @@ if ( return r; }, Object.create(null)); + // Every adaptor repeats the same item labels ('Functions', 'Overview', ...). + // Docusaurus derives a sidebar item's translation key from `key ?? label`, + // so without an explicit `key` those labels collide and the build throws + // `Multiple docs sidebar items produce the same translation key` for any + // non-default locale. Namespacing each key by adaptor keeps them unique. const items = adaptors.sort().map(a => { const base = { type: 'category', label: a.name, + key: a.name, items: [ { type: 'doc', label: 'Functions', + key: `${a.name}-functions`, id: a.docsId, }, { type: 'doc', label: 'Configuration', + key: `${a.name}-configuration`, id: a.configurationSchemaId, }, groupedJobs[a.name] && groupedJobs[a.name].length > 0 ? { type: 'category', label: 'Examples', + key: `${a.name}-examples`, items: groupedJobs[a.name].map(j => ({ type: 'doc', label: j.name, + key: `library/${j.id}`, id: `library/${j.id}`, })), } @@ -54,11 +64,13 @@ if ( { type: 'doc', label: 'Changelog', + key: `${a.name}-changelog`, id: a.changelogId, }, { type: 'doc', label: 'README.md', + key: `${a.name}-readme`, id: a.readmeId, }, ], @@ -70,6 +82,7 @@ if ( base.items.unshift({ type: 'doc', label: 'Overview', + key: `${a.name}-overview`, id: a.name, }); } @@ -88,7 +101,7 @@ if ( const extras = overviews .filter(id => !adaptors.map(a => `${a.name}`).includes(id)) - .map(id => ({ type: 'doc', id, label: id })); + .map(id => ({ type: 'doc', id, label: id, key: id })); list = [...items, ...extras].sort((a, b) => a.label.localeCompare(b.label)); } else { diff --git a/src/pages/index.js b/src/pages/index.js index 4ef9e65e6de7..40905ef9f161 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'; import clsx from 'clsx'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Translate, { translate } from '@docusaurus/Translate'; import useBaseUrl from '@docusaurus/useBaseUrl'; import Particles from 'react-particles'; import { loadFull } from 'tsparticles'; @@ -10,77 +10,104 @@ import styles from './styles.module.css'; const highlights = [ { - title: 'Job Writing Guide', + title: translate({ + id: 'homepage.highlights.jobWritingGuide.title', + message: 'Job Writing Guide', + }), link: 'documentation/jobs/job-writing-guide', - description: 'Writing a job for OpenFn? Start here', + description: translate({ + id: 'homepage.highlights.jobWritingGuide.description', + message: 'Writing a job for OpenFn? Start here', + }), }, { - title: 'CLI Usage Examples', + title: translate({ + id: 'homepage.highlights.cliUsage.title', + message: 'CLI Usage Examples', + }), link: 'documentation/cli-usage', - description: 'See what the CLI can do at a glance', + description: translate({ + id: 'homepage.highlights.cliUsage.description', + message: 'See what the CLI can do at a glance', + }), }, { - title: 'JavaScript Tips & Tricks', + title: translate({ + id: 'homepage.highlights.javascriptTips.title', + message: 'JavaScript Tips & Tricks', + }), link: 'documentation/cli-usage', - description: 'Level up your code', + description: translate({ + id: 'homepage.highlights.javascriptTips.description', + message: 'Level up your code', + }), }, ]; const features = [ { - title: 'Docs', + title: translate({ id: 'homepage.features.docs.title', message: 'Docs' }), link: 'documentation', imageUrl: 'img/undraw_Code_review_re_woeb.svg', description: ( - <> + Documentation on all aspects of OpenFn, the leading digital public good for workflow automation. - + ), }, { - title: 'Adaptors', + title: translate({ + id: 'homepage.features.adaptors.title', + message: 'Adaptors', + }), link: 'adaptors', imageUrl: 'img/undraw_pair_programming_njlp.svg', description: ( - <> + Searchable and browseable adaptors docs, examples, changelogs, and overviews for connecting the world's most common DPGs. - + ), }, { - title: 'Articles', + title: translate({ + id: 'homepage.features.articles.title', + message: 'Articles', + }), link: 'articles', imageUrl: 'img/undraw_Portfolio_update_re_jqnp.svg', description: ( - <> + How to prepare for data integration? How to structure external IDs? How to... - + ), }, { - title: 'Blog', + title: translate({ id: 'homepage.features.blog.title', message: 'Blog' }), link: 'https://openfn.org/blog', imageUrl: 'img/undraw_reading_time_gvg0.svg', description: ( - <> + We help the world's most promising social impact interventions achieve scale through automation, data integration, and interoperability. These are their stories. - + ), }, { - title: 'Enterprise', + title: translate({ + id: 'homepage.features.enterprise.title', + message: 'Enterprise', + }), link: 'https://www.openfn.org', imageUrl: 'img/undraw_secure_server_s9u8.svg', description: ( - <> + Check out the enterprise-grade OpenFn integration-platform-as-a-service (iPaaS), offering free-forever plans and affordable pathways to scale. - + ), }, ]; @@ -109,9 +136,6 @@ function Feature({ imageUrl, title, description, link }) { } function Home() { - const context = useDocusaurusContext(); - const { siteConfig = {} } = context; - const particlesInit = useCallback(async engine => { await loadFull(engine); }, []); @@ -235,7 +259,13 @@ function Home() { }; return ( - +
-

OpenFn Documentation

-

{siteConfig.tagline}

+

+ OpenFn Documentation +

+ {/* The English copy here mirrors `tagline` in docusaurus.config.js. + Site-level config values are not extracted for translation, so the + hero subtitle is declared as a translatable string instead. */} +

+ + The leading digital public good for workflow automation, OpenFn + makes ICT4D more efficient. + +

- Get Started + Get Started
@@ -271,13 +311,22 @@ function Home() { Newsletter -

Newsletter

+

+ + Newsletter + +

- Never miss a story from us, subscribe to our newsletter - here. + + Never miss a story from us, subscribe to our newsletter + here. +

- Subscribe + + Subscribe +
@@ -312,7 +366,11 @@ function Home() { )}
-

✨Documentation Highlights✨

+

+ + ✨Documentation Highlights✨ + +

{highlights.map(h => (
From ed1912db3213c123f18b0525c9d29fd2b6c71d68 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 14:30:10 +0100 Subject: [PATCH 02/34] Keep homepage highlight links in the reader's locale The highlight cards used a plain with a relative path, which dropped the locale prefix. Use Link with an absolute path so baseUrl, including /es/, is applied. --- src/pages/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/index.js b/src/pages/index.js index 40905ef9f161..85c4d6962156 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -375,7 +375,7 @@ function Home() { {highlights.map(h => (

- {h.title} + {h.title}

{h.description}

From 3e8470eeed20fe0e88304a7512a0367c59de30f2 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 14:42:10 +0100 Subject: [PATCH 03/34] Ask before editing sidebars-adaptors.js It generates the adaptor sidebar and now carries the keys that let a second locale build, so an edit there can break translated builds without touching English. --- AGENTS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1974d5a46e96..1a034f75b394 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,8 +25,8 @@ you need. **Ask before editing** -- `docusaurus.config.js`, `package.json`, and anything in `.github/`. These - change how the site builds and deploys. +- `docusaurus.config.js`, `package.json`, `sidebars-adaptors.js`, and anything + in `.github/`. These change how the site builds and deploys. **Special rules apply** From df9441419cf95103ffd93bdef688b9391d4c562c Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 15:30:42 +0100 Subject: [PATCH 04/34] Turn on Spanish without linking to it Build the es locale at /es/ so the translate skill can run against it, but keep the language dropdown out of the navbar until Spanish is ready for readers. --- docusaurus.config.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index 8fb30e9a972c..dc9832a74aac 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -12,17 +12,24 @@ module.exports = { favicon: 'img/favicon.ico', organizationName: 'openfn', projectName: 'docs', - // English only for now. Translated content will live in i18n//, - // and anything not translated falls back to the English source. + // --- i18n (internationalization) --- + // Spanish is built at /es/ but not linked from the navbar until it launches. + // Translated content lives in i18n//. Anything not translated + // falls back to the English source automatically. i18n: { defaultLocale: 'en', - locales: ['en'], + locales: ['en', 'es'], localeConfigs: { en: { label: 'English', direction: 'ltr', htmlLang: 'en', }, + es: { + label: 'Español', + direction: 'ltr', + htmlLang: 'es', + }, }, }, markdown: { From aa0d453a9c0aa6c26ef8bc180e123f65dc04d370 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 15:30:42 +0100 Subject: [PATCH 05/34] Explain how to run the site in Spanish locally --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index ac0049786e52..f1f105e94b5f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,16 @@ yarn start-offline This command skips the adaptor docs step, which requires an active internet connection. +### Start in another language + +``` +yarn start --locale es +``` + +The development server shows one language at a time. This serves the Spanish +site at the root, with English wherever a page isn't translated yet. To see +both languages together at `/` and `/es/`, build and serve the site as below. + ### Building the job library ``` From d3e0675795cfaefe9d61b71ff08203b10a7b35ef Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 17:05:32 +0100 Subject: [PATCH 06/34] Close the Workflows callout with ::: The callout on the terminology page ended with `:::note` instead of `:::`. --- docs/get-started/terminology.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/get-started/terminology.md b/docs/get-started/terminology.md index 88f1bae70e80..d638d9f8b4bb 100644 --- a/docs/get-started/terminology.md +++ b/docs/get-started/terminology.md @@ -84,7 +84,7 @@ Workflows are fully configurable and reusable. They can also be chained together to automate multi-step processes and two-way data syncs to keep data consistent between multiple applications (using multi-app Saga patterns). -:::note +::: ### Adaptor From c953079fc29fde7dbe3dc28b81228bb47e7ee9a3 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 17:05:38 +0100 Subject: [PATCH 07/34] Mark state as the state object on the terminology page "initial state" and "final state" read as ordinary words, so translations turned them into ordinary words too. Formatting them as `state` makes it clear they name the object, which glossary.yml keeps in English. --- docs/get-started/terminology.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/get-started/terminology.md b/docs/get-started/terminology.md index d638d9f8b4bb..6a3d4db1e7db 100644 --- a/docs/get-started/terminology.md +++ b/docs/get-started/terminology.md @@ -243,8 +243,8 @@ The Inspector has 3 key interfaces: `Input`, `Editor`, & `Output`. ### Input An Input is the data (`json`) that is used as the starting Input for a Workflow -Step to utilise when it's run. Every Run will have an Input (initial state) and -Output (final state). +Step to utilise when it's run. Every Run has an Input (the initial `state`) and +an Output (the final `state`). Inputs may be created automatically by a webhook event (e.g., a message forwarded or JSON payload posted to OpenFn) or another Workflow Step, or From 2f5595364d721662e9ac686ef9e3adac1ff5f21c Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 17:23:14 +0100 Subject: [PATCH 08/34] Split the translate skill into pages and interface tasks SKILL.md keeps the shared rules and points to pages.md (translate a set of pages) or interface.md (the navbar, footer, sidebar headings and homepage). interface.md is new: it says which write-translations output to keep and which to remove. The skill no longer names French; it translates into whichever locales are enabled. --- .agents/skills/translate/SKILL.md | 136 ++++++-------------------- .agents/skills/translate/interface.md | 38 +++++++ .agents/skills/translate/pages.md | 102 +++++++++++++++++++ AGENTS.md | 3 +- 4 files changed, 173 insertions(+), 106 deletions(-) create mode 100644 .agents/skills/translate/interface.md create mode 100644 .agents/skills/translate/pages.md diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index 8b2b91b2b91a..6394b3b84e15 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -1,29 +1,34 @@ --- name: translate description: - Translates English docs pages into Spanish and French under i18n/, respecting - glossary.yml, translation-rules.yml, review status, and do-not-retranslate - fences, and opens one PR per locale. Use when asked to translate or refresh - translations. + Translates the docs under i18n/ into each language enabled in + docusaurus.config.js, either a set of English pages or the interface text + (navbar, footer, sidebar headings, homepage). Respects glossary.yml, translation-rules.yml, review status, and + do-not-retranslate fences, and opens one PR per locale. Use when asked to + translate or refresh translations. disable-model-invocation: true --- # Translate -Translate English docs into Spanish (`es`) and French (`fr`). The English is -always the source of truth. Translations are generated files that live in -this repo. Save each one at the same path as the English page, under -`i18n//docusaurus-plugin-content-docs/current/`. For example, -`docs/build/triggers.md` goes to -`i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md`. -Docusaurus ignores a file anywhere else without an error, and the page stays -English. - -The sidebar headings come from `sidebars-main.js`, not from the pages. If it -has new or renamed entries, run -`yarn docusaurus write-translations --locale `. This adds them to -`i18n//docusaurus-plugin-content-docs/current.json` in English and -keeps the ones already translated. Translate the new ones. +Translate English docs into each locale in `i18n.locales` in +`docusaurus.config.js`, other than English. The English is always the source +of truth. Translations are generated files that live in +this repo, under `i18n//`. + +There are two tasks. Each has its own file in this folder; read the one you +need. + +- **`/translate pages `** translates a set of pages. The scope is one + page, one folder under `docs/`, or one sidebar category. With no scope, it + covers every page that needs it. See `pages.md`. +- **`/translate interface`** translates the text that is not in a page: the + navbar, footer, sidebar headings, and homepage. Run it when + `sidebars-main.js`, the navbar or footer in `docusaurus.config.js`, or the + homepage in `src/pages/` has changed. See `interface.md`. + +If you are not told which task, work out which ones are needed from what has +changed in English, say so, and ask before starting. Never translate the generated adaptor pages, the job library, the old v1 docs, or articles and blog posts. @@ -37,103 +42,24 @@ Check these three things. If any fails, stop and ask. - `i18n/` is not in `.gitignore`. - `glossary.yml` and `translation-rules.yml` are valid YAML. -Translate the English page exactly as it is on disk, so the hash you record -matches what you translated. Do not reformat it; English changes belong in -their own PR. After writing the translation, run -`yarn prettier --write ` on only the files you changed under `i18n/`. - -## Front matter - -Copy the English page's front matter. Translate only `title` and -`sidebar_label`. Then add: - -```yaml -translation_source_hash: -translation_review_status: machine -``` - -The hash is the content hash of the English file, from -`git hash-object docs/.md`, not a commit. Commits do not survive squash -merges: a hash pointing at a commit made on a branch dangles as soon as the -branch is squashed onto main. A content hash is the same wherever the file -lives, and it answers the only question the field exists to answer: is the -English still the version this was translated from? To compare, hash the -current English file and check it against the recorded value. - -`translation_review_status` can be `machine`, `needs-review`, or -`human-reviewed`. Only a human ever sets `human-reviewed`, and when they do -they also add `translation_reviewer` and `translation_review_date`. - -## Decide what to do with each page - -- **No translation yet.** Translate the whole page. -- **The hash matches the current English file.** Skip it, whatever its - status. The English has not changed since it was translated. The one - exception: if `glossary.yml` or `translation-rules.yml` was committed more - recently than the translation (compare `git log -1 --format=%ct -- `), - treat a `machine` page as if the hash no longer matches, so it picks up the - new rules. -- **The hash no longer matches, and the status is `machine`, `needs-review`, - or missing.** Translate the whole page again, but keep any fenced blocks - (see below) exactly as they were. -- **The hash no longer matches, and the status is `human-reviewed`.** Leave - the file out of the translation PR. Instead, open a separate PR for the - named reviewer that changes only the affected parts. Recover the English the - reviewer saw with `git cat-file -p `, diff it against the - current English, and translate only what changed. In the same PR, set - `translation_source_hash` to the current English hash and leave the status - as `human-reviewed`: the reviewer merging it approves it. If the old version - is no longer in the repo, say so and offer a full retranslation in that PR - instead. - -## Fenced blocks - -A human can wrap part of a translation like this: - -```markdown - -Text a reviewer has corrected by hand. - -``` - -Copy those blocks into the new translation exactly, in the same place. If the -English they correspond to has been deleted, keep the block anyway and ask -what to do with it. - ## How to translate +These apply to both tasks. + - Words in `glossary.yml` stay in English. For ordinary words that are also product terms, like "run" or "step", keep the English only when the word means the OpenFn thing. - Follow any rules for the locale in `translation-rules.yml`. By default, - Spanish uses "tú" and French uses "vous". + Spanish uses "tú". - Copy code blocks and inline code exactly. You may translate comments inside code. - Keep the names of things in the app, like buttons, menus, tabs, and field labels, exactly as they are in the English. The app is English only, so a translated button name points the reader at a button that does not exist. -- Keep the same structure: same headings at the same levels, same lists, - same callouts, same components. -- Keep links exactly as they are in the English. Do not add `/es/` or - `/fr/`; Docusaurus adds the locale when it builds the page. If the English - has a relative link like `../deploy/portability.md`, it breaks the - translated build, so fix it in the English first (see the house style in - `AGENTS.md`). -- Give translated headings the original English anchor so existing links - still work. - -## Before you commit - -Check that the fixed glossary terms (the ones without `product_noun: true`, -such as OpenFn, Lightning, adaptor, webhook) appear as many times as in the -English. Product nouns like "run" and "step" are allowed to differ, since -their ordinary-English uses get translated. Before counting, join each file -into one line with single spaces: Prettier wraps prose at 80 columns, and -English and Spanish wrap at different points, so a multi-word term like "work -order" can sit across a line break in one file and not the other. Check the -code blocks are identical. Check the counts of headings, code blocks, -callouts, images, and tables match. Check the front matter is complete. Check -every fenced block survived. Then build the site and make sure it passes: + +## Before you open the PR + +Build the site and make sure it passes: ```bash yarn generate-library @@ -148,5 +74,5 @@ as broken. Open one PR per locale, separate from the English PR. Translated files do not count toward the 20-file limit, because a locale's translations are reviewed as a set. In the PR description, say which tool and model translated the -pages. If you spot a problem in the English while translating, note it for +text. If you spot a problem in the English while translating, note it for the next English pass; do not fix it here. diff --git a/.agents/skills/translate/interface.md b/.agents/skills/translate/interface.md new file mode 100644 index 000000000000..e1196fe7416f --- /dev/null +++ b/.agents/skills/translate/interface.md @@ -0,0 +1,38 @@ +# Translate the interface text + +Read `SKILL.md` first. It has the checks to run before you start, the +translation rules, and how to build and open the PR. + +The navbar, footer, sidebar headings, and homepage are not pages. Their text +lives in JSON files under `i18n//`. Create or update them with: + +```bash +yarn docusaurus write-translations --locale +``` + +This adds new entries in English and keeps the ones already translated. +Translate the `message` value of each new entry. Leave the keys and +`description` values alone. + +It writes more than we want. Keep only these: + +| File | Keep | +| --------------------------------------------- | -------------------------------------------------------------------------- | +| `code.json` | The `homepage.*` entries | +| `docusaurus-theme-classic/navbar.json` | Everything. Leave `title` and `logo.alt` as `OpenFn` | +| `docusaurus-theme-classic/footer.json` | Everything except `copyright` | +| `docusaurus-plugin-content-docs/current.json` | Everything. These are the sidebar headings. Leave `version.label` as it is | + +Remove the rest before you commit: + +- The `theme.*` entries in `code.json`. Docusaurus already ships them + translated, and a copy here would override theirs and go stale when Docusaurus + is upgraded. +- The `copyright` entry in `footer.json`. The site works out the year when it + builds, and a translated copy would freeze it. +- Every other file: the adaptor sidebar + (`docusaurus-plugin-content-docs-adaptors/`), the old v1 docs + (`version-legacy.json`), and blog and articles + (`docusaurus-plugin-content-blog*/`). Those stay in English. + +Then build and open the PR as `SKILL.md` describes. diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md new file mode 100644 index 000000000000..29e62dfad47c --- /dev/null +++ b/.agents/skills/translate/pages.md @@ -0,0 +1,102 @@ +# Translate pages + +Read `SKILL.md` first. It has the checks to run before you start, the +translation rules, and how to build and open the PR. + +Save each translation at the same path as the English page, under +`i18n//docusaurus-plugin-content-docs/current/`. For example, +`docs/build/triggers.md` goes to +`i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md`. +Docusaurus ignores a file anywhere else without an error, and the page stays +English. + +Translate the English page exactly as it is on disk, so the hash you record +matches what you translated. Do not reformat it; English changes belong in +their own PR. After writing the translation, run +`yarn prettier --write ` on only the files you changed under `i18n/`. + +## Front matter + +Copy the English page's front matter. Translate only `title` and +`sidebar_label`. Then add: + +```yaml +translation_source_hash: +translation_review_status: machine +``` + +The hash is the content hash of the English file, from +`git hash-object docs/.md`, not a commit. Commits do not survive squash +merges: a hash pointing at a commit made on a branch dangles as soon as the +branch is squashed onto main. A content hash is the same wherever the file +lives, and it answers the only question the field exists to answer: is the +English still the version this was translated from? To compare, hash the +current English file and check it against the recorded value. + +`translation_review_status` can be `machine`, `needs-review`, or +`human-reviewed`. Only a human ever sets `human-reviewed`, and when they do +they also add `translation_reviewer` and `translation_review_date`. + +## Decide what to do with each page + +- **No translation yet.** Translate the whole page. +- **The hash matches the current English file.** Skip it, whatever its + status. The English has not changed since it was translated. The one + exception: if `glossary.yml` or `translation-rules.yml` was committed more + recently than the translation (compare `git log -1 --format=%ct -- `), + treat a `machine` page as if the hash no longer matches, so it picks up the + new rules. +- **The hash no longer matches, and the status is `machine`, `needs-review`, + or missing.** Translate the whole page again, but keep any fenced blocks + (see below) exactly as they were. +- **The hash no longer matches, and the status is `human-reviewed`.** Leave + the file out of the translation PR. Instead, open a separate PR for the + named reviewer that changes only the affected parts. Recover the English the + reviewer saw with `git cat-file -p `, diff it against the + current English, and translate only what changed. In the same PR, set + `translation_source_hash` to the current English hash and leave the status + as `human-reviewed`: the reviewer merging it approves it. If the old version + is no longer in the repo, say so and offer a full retranslation in that PR + instead. + +## Fenced blocks + +A human can wrap part of a translation like this: + +```markdown + +Text a reviewer has corrected by hand. + +``` + +Copy those blocks into the new translation exactly, in the same place. If the +English they correspond to has been deleted, keep the block anyway and ask +what to do with it. + +## Page rules + +These are on top of the translation rules in `SKILL.md`. + +- Keep the same structure: same headings at the same levels, same lists, + same callouts, same components. +- Keep links exactly as they are in the English. Do not add the locale, like + `/es/`; Docusaurus adds it when it builds the page. If the English + has a relative link like `../deploy/portability.md`, it breaks the + translated build, so fix it in the English first (see the house style in + `AGENTS.md`). +- Give translated headings the original English anchor so existing links + still work. + +## Check each page + +Check that the fixed glossary terms (the ones without `product_noun: true`, +such as OpenFn, Lightning, adaptor, webhook) appear as many times as in the +English. Product nouns like "run" and "step" are allowed to differ, since +their ordinary-English uses get translated. Before counting, join each file +into one line with single spaces: Prettier wraps prose at 80 columns, and +English and Spanish wrap at different points, so a multi-word term like "work +order" can sit across a line break in one file and not the other. Check the +code blocks are identical. Check the counts of headings, code blocks, +callouts, images, and tables match. Check the front matter is complete. Check +every fenced block survived. Then build and open the PR as `SKILL.md` +describes. diff --git a/AGENTS.md b/AGENTS.md index 1a034f75b394..2ab149308456 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,7 +48,8 @@ Never change them. or from an identify-gaps report. - **`release-review`** works out what the product shipped recently and passes that to identify-gaps. Suited to a monthly schedule. -- **`translate`** translates English pages, in its own PR per locale. +- **`translate`** translates English pages (`/translate pages`) or the + interface text (`/translate interface`), in its own PR per locale. `update-content` and `translate` open PRs, so they only run when someone asks for them by name (`/update-content`, `/translate`). When another skill hands off From e064029e243dfd5328729f090914e7ddc925c49a Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 17:28:24 +0100 Subject: [PATCH 09/34] Say where the translate skill records problems in the English --- .agents/skills/translate/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index 6394b3b84e15..a12efbb4f322 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -74,5 +74,6 @@ as broken. Open one PR per locale, separate from the English PR. Translated files do not count toward the 20-file limit, because a locale's translations are reviewed as a set. In the PR description, say which tool and model translated the -text. If you spot a problem in the English while translating, note it for -the next English pass; do not fix it here. +text. If you spot a problem in the English while translating, list it in the +PR description under "Problems in the English", with the file and line. Do +not fix it in this PR. From 17a8e80f2c29597aab36c811cb16f5944a314bbc Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 17:36:55 +0100 Subject: [PATCH 10/34] Keep custom code.json strings and format the translate skill Remove only theme.* from code.json so new custom strings are not deleted. Show the do-not-retranslate example as Prettier leaves it, and run Prettier on the skill files and AGENTS.md. --- .agents/skills/translate/SKILL.md | 34 +++++----- .agents/skills/translate/interface.md | 2 +- .agents/skills/translate/pages.md | 89 +++++++++++++-------------- AGENTS.md | 10 +-- 4 files changed, 66 insertions(+), 69 deletions(-) diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index a12efbb4f322..b55ae2038805 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -3,18 +3,18 @@ name: translate description: Translates the docs under i18n/ into each language enabled in docusaurus.config.js, either a set of English pages or the interface text - (navbar, footer, sidebar headings, homepage). Respects glossary.yml, translation-rules.yml, review status, and - do-not-retranslate fences, and opens one PR per locale. Use when asked to - translate or refresh translations. + (navbar, footer, sidebar headings, homepage). Respects glossary.yml, + translation-rules.yml, review status, and do-not-retranslate fences, and opens + one PR per locale. Use when asked to translate or refresh translations. disable-model-invocation: true --- # Translate Translate English docs into each locale in `i18n.locales` in -`docusaurus.config.js`, other than English. The English is always the source -of truth. Translations are generated files that live in -this repo, under `i18n//`. +`docusaurus.config.js`, other than English. The English is always the source of +truth. Translations are generated files that live in this repo, under +`i18n//`. There are two tasks. Each has its own file in this folder; read the one you need. @@ -30,8 +30,8 @@ need. If you are not told which task, work out which ones are needed from what has changed in English, say so, and ask before starting. -Never translate the generated adaptor pages, the job library, the old v1 -docs, or articles and blog posts. +Never translate the generated adaptor pages, the job library, the old v1 docs, +or articles and blog posts. ## Before you start @@ -47,8 +47,8 @@ Check these three things. If any fails, stop and ask. These apply to both tasks. - Words in `glossary.yml` stay in English. For ordinary words that are also - product terms, like "run" or "step", keep the English only when the word - means the OpenFn thing. + product terms, like "run" or "step", keep the English only when the word means + the OpenFn thing. - Follow any rules for the locale in `translation-rules.yml`. By default, Spanish uses "tú". - Copy code blocks and inline code exactly. You may translate comments inside @@ -68,12 +68,12 @@ yarn build ``` Build the whole site, not just your locale. `yarn build --locale ` -builds the locale at the site root, so every correct `/es/...` link shows up -as broken. +builds the locale at the site root, so every correct `/es/...` link shows up as +broken. Open one PR per locale, separate from the English PR. Translated files do not -count toward the 20-file limit, because a locale's translations are reviewed -as a set. In the PR description, say which tool and model translated the -text. If you spot a problem in the English while translating, list it in the -PR description under "Problems in the English", with the file and line. Do -not fix it in this PR. +count toward the 20-file limit, because a locale's translations are reviewed as +a set. In the PR description, say which tool and model translated the text. If +you spot a problem in the English while translating, list it in the PR +description under "Problems in the English", with the file and line. Do not fix +it in this PR. diff --git a/.agents/skills/translate/interface.md b/.agents/skills/translate/interface.md index e1196fe7416f..484779563ea1 100644 --- a/.agents/skills/translate/interface.md +++ b/.agents/skills/translate/interface.md @@ -18,7 +18,7 @@ It writes more than we want. Keep only these: | File | Keep | | --------------------------------------------- | -------------------------------------------------------------------------- | -| `code.json` | The `homepage.*` entries | +| `code.json` | Everything except the `theme.*` entries | | `docusaurus-theme-classic/navbar.json` | Everything. Leave `title` and `logo.alt` as `OpenFn` | | `docusaurus-theme-classic/footer.json` | Everything except `copyright` | | `docusaurus-plugin-content-docs/current.json` | Everything. These are the sidebar headings. Leave `version.label` as it is | diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 29e62dfad47c..5538f81c86bd 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -6,14 +6,13 @@ translation rules, and how to build and open the PR. Save each translation at the same path as the English page, under `i18n//docusaurus-plugin-content-docs/current/`. For example, `docs/build/triggers.md` goes to -`i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md`. -Docusaurus ignores a file anywhere else without an error, and the page stays -English. +`i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md`. Docusaurus +ignores a file anywhere else without an error, and the page stays English. Translate the English page exactly as it is on disk, so the hash you record -matches what you translated. Do not reformat it; English changes belong in -their own PR. After writing the translation, run -`yarn prettier --write ` on only the files you changed under `i18n/`. +matches what you translated. Do not reformat it; English changes belong in their +own PR. After writing the translation, run `yarn prettier --write ` on +only the files you changed under `i18n/`. ## Front matter @@ -30,33 +29,32 @@ The hash is the content hash of the English file, from merges: a hash pointing at a commit made on a branch dangles as soon as the branch is squashed onto main. A content hash is the same wherever the file lives, and it answers the only question the field exists to answer: is the -English still the version this was translated from? To compare, hash the -current English file and check it against the recorded value. +English still the version this was translated from? To compare, hash the current +English file and check it against the recorded value. `translation_review_status` can be `machine`, `needs-review`, or -`human-reviewed`. Only a human ever sets `human-reviewed`, and when they do -they also add `translation_reviewer` and `translation_review_date`. +`human-reviewed`. Only a human ever sets `human-reviewed`, and when they do they +also add `translation_reviewer` and `translation_review_date`. ## Decide what to do with each page - **No translation yet.** Translate the whole page. -- **The hash matches the current English file.** Skip it, whatever its - status. The English has not changed since it was translated. The one - exception: if `glossary.yml` or `translation-rules.yml` was committed more - recently than the translation (compare `git log -1 --format=%ct -- `), - treat a `machine` page as if the hash no longer matches, so it picks up the - new rules. -- **The hash no longer matches, and the status is `machine`, `needs-review`, - or missing.** Translate the whole page again, but keep any fenced blocks - (see below) exactly as they were. -- **The hash no longer matches, and the status is `human-reviewed`.** Leave - the file out of the translation PR. Instead, open a separate PR for the - named reviewer that changes only the affected parts. Recover the English the +- **The hash matches the current English file.** Skip it, whatever its status. + The English has not changed since it was translated. The one exception: if + `glossary.yml` or `translation-rules.yml` was committed more recently than the + translation (compare `git log -1 --format=%ct -- `), treat a `machine` + page as if the hash no longer matches, so it picks up the new rules. +- **The hash no longer matches, and the status is `machine`, `needs-review`, or + missing.** Translate the whole page again, but keep any fenced blocks (see + below) exactly as they were. +- **The hash no longer matches, and the status is `human-reviewed`.** Leave the + file out of the translation PR. Instead, open a separate PR for the named + reviewer that changes only the affected parts. Recover the English the reviewer saw with `git cat-file -p `, diff it against the current English, and translate only what changed. In the same PR, set - `translation_source_hash` to the current English hash and leave the status - as `human-reviewed`: the reviewer merging it approves it. If the old version - is no longer in the repo, say so and offer a full retranslation in that PR + `translation_source_hash` to the current English hash and leave the status as + `human-reviewed`: the reviewer merging it approves it. If the old version is + no longer in the repo, say so and offer a full retranslation in that PR instead. ## Fenced blocks @@ -65,38 +63,37 @@ A human can wrap part of a translation like this: ```markdown + Text a reviewer has corrected by hand. ``` Copy those blocks into the new translation exactly, in the same place. If the -English they correspond to has been deleted, keep the block anyway and ask -what to do with it. +English they correspond to has been deleted, keep the block anyway and ask what +to do with it. ## Page rules These are on top of the translation rules in `SKILL.md`. -- Keep the same structure: same headings at the same levels, same lists, - same callouts, same components. +- Keep the same structure: same headings at the same levels, same lists, same + callouts, same components. - Keep links exactly as they are in the English. Do not add the locale, like - `/es/`; Docusaurus adds it when it builds the page. If the English - has a relative link like `../deploy/portability.md`, it breaks the - translated build, so fix it in the English first (see the house style in - `AGENTS.md`). -- Give translated headings the original English anchor so existing links - still work. + `/es/`; Docusaurus adds it when it builds the page. If the English has a + relative link like `../deploy/portability.md`, it breaks the translated build, + so fix it in the English first (see the house style in `AGENTS.md`). +- Give translated headings the original English anchor so existing links still + work. ## Check each page -Check that the fixed glossary terms (the ones without `product_noun: true`, -such as OpenFn, Lightning, adaptor, webhook) appear as many times as in the -English. Product nouns like "run" and "step" are allowed to differ, since -their ordinary-English uses get translated. Before counting, join each file -into one line with single spaces: Prettier wraps prose at 80 columns, and -English and Spanish wrap at different points, so a multi-word term like "work -order" can sit across a line break in one file and not the other. Check the -code blocks are identical. Check the counts of headings, code blocks, -callouts, images, and tables match. Check the front matter is complete. Check -every fenced block survived. Then build and open the PR as `SKILL.md` -describes. +Check that the fixed glossary terms (the ones without `product_noun: true`, such +as OpenFn, Lightning, adaptor, webhook) appear as many times as in the English. +Product nouns like "run" and "step" are allowed to differ, since their +ordinary-English uses get translated. Before counting, join each file into one +line with single spaces: Prettier wraps prose at 80 columns, and English and +Spanish wrap at different points, so a multi-word term like "work order" can sit +across a line break in one file and not the other. Check the code blocks are +identical. Check the counts of headings, code blocks, callouts, images, and +tables match. Check the front matter is complete. Check every fenced block +survived. Then build and open the PR as `SKILL.md` describes. diff --git a/AGENTS.md b/AGENTS.md index 2ab149308456..7ee6d7d1e5da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,8 +48,8 @@ Never change them. or from an identify-gaps report. - **`release-review`** works out what the product shipped recently and passes that to identify-gaps. Suited to a monthly schedule. -- **`translate`** translates English pages (`/translate pages`) or the - interface text (`/translate interface`), in its own PR per locale. +- **`translate`** translates English pages (`/translate pages`) or the interface + text (`/translate interface`), in its own PR per locale. `update-content` and `translate` open PRs, so they only run when someone asks for them by name (`/update-content`, `/translate`). When another skill hands off @@ -80,9 +80,9 @@ category from the sidebar, one folder under `docs/`, or one page. Stop at 20 changed files and open a PR (see `update-content/SKILL.md`). Translations go in their own PR per locale and do not count toward the 20. -Mechanical changes that the build or a script checks, such as rewriting links -or running Prettier, can go in one PR of any size. Keep that PR to the -mechanical change only, so it stays quick to review. +Mechanical changes that the build or a script checks, such as rewriting links or +running Prettier, can go in one PR of any size. Keep that PR to the mechanical +change only, so it stays quick to review. ## Checking a change From 260d4e0d7cc54988f85bd92d35bba5b5756d58ec Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Wed, 30 Sep 2026 17:53:18 +0100 Subject: [PATCH 11/34] Keep the blog and articles titles in the translate skill These files hold only the section titles and sidebar heading, not the posts, so they are translated like the rest of the interface. --- .agents/skills/translate/interface.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.agents/skills/translate/interface.md b/.agents/skills/translate/interface.md index 484779563ea1..92ae7726bc29 100644 --- a/.agents/skills/translate/interface.md +++ b/.agents/skills/translate/interface.md @@ -16,12 +16,13 @@ Translate the `message` value of each new entry. Leave the keys and It writes more than we want. Keep only these: -| File | Keep | -| --------------------------------------------- | -------------------------------------------------------------------------- | -| `code.json` | Everything except the `theme.*` entries | -| `docusaurus-theme-classic/navbar.json` | Everything. Leave `title` and `logo.alt` as `OpenFn` | -| `docusaurus-theme-classic/footer.json` | Everything except `copyright` | -| `docusaurus-plugin-content-docs/current.json` | Everything. These are the sidebar headings. Leave `version.label` as it is | +| File | Keep | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `code.json` | Everything except the `theme.*` entries | +| `docusaurus-theme-classic/navbar.json` | Everything. Leave `title` and `logo.alt` as `OpenFn` | +| `docusaurus-theme-classic/footer.json` | Everything except `copyright` | +| `docusaurus-plugin-content-docs/current.json` | Everything. These are the sidebar headings. Leave `version.label` as it is | +| `docusaurus-plugin-content-blog*/options.json` | Everything. These are the titles and sidebar heading of the blog and articles, not the posts | Remove the rest before you commit: @@ -31,8 +32,7 @@ Remove the rest before you commit: - The `copyright` entry in `footer.json`. The site works out the year when it builds, and a translated copy would freeze it. - Every other file: the adaptor sidebar - (`docusaurus-plugin-content-docs-adaptors/`), the old v1 docs - (`version-legacy.json`), and blog and articles - (`docusaurus-plugin-content-blog*/`). Those stay in English. + (`docusaurus-plugin-content-docs-adaptors/`) and the old v1 docs + (`version-legacy.json`). Those stay in English. Then build and open the PR as `SKILL.md` describes. From 485dbaeee6c0bc7eb0bc00d92ee0b60e4c722a62 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 09:24:06 +0100 Subject: [PATCH 12/34] Let project, credential, collection and operation be translated --- glossary.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/glossary.yml b/glossary.yml index 59134c92180e..281e978084af 100644 --- a/glossary.yml +++ b/glossary.yml @@ -84,11 +84,6 @@ terms: "correct" v1 usage inside pages that are explicitly about v1 or migration. - - term: credential - translate: false - product_noun: true - note: Stored authentication configuration attached to a Step. - - term: trigger translate: false product_noun: true @@ -128,11 +123,6 @@ terms: translate: false note: Billing and hosting unit on the hosted OpenFn app. - - term: project - translate: false - product_noun: true - note: Administrative grouping of workflows, credentials, and collaborators. - - term: dataclip translate: false variants: @@ -140,11 +130,6 @@ terms: - data-clip note: A stored input or output state object. - - term: collection - translate: false - product_noun: true - note: The Collections key-value store feature. Ordinary English use may be translated. - - term: sandbox translate: false product_noun: true @@ -172,11 +157,6 @@ terms: The `state` object passed between operations. Protected only when it names the object (usually rendered in code as `state`). - - term: operation - translate: false - product_noun: true - note: A function exported by an adaptor, e.g. `get()`, `upsert()`. - patterns: - pattern: "@openfn/[a-z0-9-]+" note: npm package names (adaptors, CLI, runtime). From d0fa81e972877ec405ced97ad0f3e06402565e4e Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 09:33:30 +0100 Subject: [PATCH 13/34] Add project, credential, collection and operation back to the glossary They now have a Spanish translation pinned under locales. --- glossary.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/glossary.yml b/glossary.yml index 281e978084af..72444588217e 100644 --- a/glossary.yml +++ b/glossary.yml @@ -84,6 +84,15 @@ terms: "correct" v1 usage inside pages that are explicitly about v1 or migration. + - term: credential + translate: true + note: >- + Stored authentication configuration attached to a Step. Spanish translates + it because "credencial" looks almost the same as the English in the app. + Decide separately for each new locale. + locales: + es: credencial + - term: trigger translate: false product_noun: true @@ -123,6 +132,15 @@ terms: translate: false note: Billing and hosting unit on the hosted OpenFn app. + - term: project + translate: true + note: >- + Administrative grouping of workflows, credentials, and collaborators. + Spanish translates it because "proyecto" looks almost the same as the + English in the app. Decide separately for each new locale. + locales: + es: proyecto + - term: dataclip translate: false variants: @@ -130,6 +148,15 @@ terms: - data-clip note: A stored input or output state object. + - term: collection + translate: true + note: >- + The Collections key-value store feature. Spanish translates it because + "colección" looks almost the same as the English in the app. Decide + separately for each new locale. + locales: + es: colección + - term: sandbox translate: false product_noun: true @@ -157,6 +184,15 @@ terms: The `state` object passed between operations. Protected only when it names the object (usually rendered in code as `state`). + - term: operation + translate: true + note: >- + A function exported by an adaptor, e.g. `get()`, `upsert()`. Spanish + translates it because "operación" looks almost the same as the English in + the app. Decide separately for each new locale. + locales: + es: operación + patterns: - pattern: "@openfn/[a-z0-9-]+" note: npm package names (adaptors, CLI, runtime). From 51ba0693ef5995f1790cb623fdb24f559099709a Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 12:17:22 +0100 Subject: [PATCH 14/34] Translate only changed blocks, and add a side-by-side review script The translate skill now keeps the translation of every block whose English has not changed, and checks glossary terms block by block. --- .agents/skills/translate/pages.md | 64 +++++- .agents/skills/translate/side-by-side.js | 274 +++++++++++++++++++++++ 2 files changed, 327 insertions(+), 11 deletions(-) create mode 100644 .agents/skills/translate/side-by-side.js diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 5538f81c86bd..9dfb7baaf430 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -42,11 +42,10 @@ also add `translation_reviewer` and `translation_review_date`. - **The hash matches the current English file.** Skip it, whatever its status. The English has not changed since it was translated. The one exception: if `glossary.yml` or `translation-rules.yml` was committed more recently than the - translation (compare `git log -1 --format=%ct -- `), treat a `machine` - page as if the hash no longer matches, so it picks up the new rules. + translation (compare `git log -1 --format=%ct -- `), translate a + `machine` page again in full, so it picks up the new rules. - **The hash no longer matches, and the status is `machine`, `needs-review`, or - missing.** Translate the whole page again, but keep any fenced blocks (see - below) exactly as they were. + missing.** Translate only what changed (see "Updating a page" below). - **The hash no longer matches, and the status is `human-reviewed`.** Leave the file out of the translation PR. Instead, open a separate PR for the named reviewer that changes only the affected parts. Recover the English the @@ -57,6 +56,43 @@ also add `translation_reviewer` and `translation_review_date`. no longer in the repo, say so and offer a full retranslation in that PR instead. +## Updating a page + +Retranslating a whole page rewords text that has not changed, and a reviewer can +no longer see what did. So translate only the blocks whose English changed: + +```bash +node .agents/skills/translate/side-by-side.js docs/.md --json +``` + +This lists the blocks of the current English. Each has the existing +`translation` to reuse, or `null` where the English is new or changed. Write the +page as those blocks in order, separated by blank lines: copy each reused +translation exactly, and translate each `null` block. Then update the front +matter and run Prettier as usual. + +- A block with `fenced: true` was inside a `` fence. + Put the fence back around it. If `unusedFenced` is not empty, the English a + fenced block corresponds to has changed or gone. Keep the block and ask what + to do with it. +- If the result has an `error`, or `aligned` is `false`, the old translation + cannot be matched to its English. Translate the whole page again, keeping + fenced blocks, and say so in the PR. + +In the PR description, list for each page how many blocks were translated. + +## Reviewing + +To read a translation next to the English it was translated from: + +```bash +node .agents/skills/translate/side-by-side.js docs/.md... > review.html +``` + +Add `--base `, such as `--base origin/i18n`, to highlight the blocks that +changed since the translation at that ref. Put both commands in the PR +description, with the page paths filled in. + ## Fenced blocks A human can wrap part of a translation like this: @@ -87,13 +123,19 @@ These are on top of the translation rules in `SKILL.md`. ## Check each page -Check that the fixed glossary terms (the ones without `product_noun: true`, such -as OpenFn, Lightning, adaptor, webhook) appear as many times as in the English. -Product nouns like "run" and "step" are allowed to differ, since their -ordinary-English uses get translated. Before counting, join each file into one -line with single spaces: Prettier wraps prose at 80 columns, and English and -Spanish wrap at different points, so a multi-word term like "work order" can sit -across a line break in one file and not the other. Check the code blocks are +Check that no fixed glossary term was translated: + +```bash +node .agents/skills/translate/side-by-side.js docs/.md... --check --base HEAD +``` + +This lists each block where a fixed term (OpenFn, Lightning, adaptor, work +order, and so on) appears fewer times in the translation than in the English. +With `--base HEAD`, it checks only blocks whose English changed since the last +commit, so a block that was already looked at is not raised again. Look at each +one. If the term was translated, put the English term back. If the sentence just +uses it fewer times, for example because Spanish drops a repeated subject, leave +it: do not add the term back to match the count. Check the code blocks are identical. Check the counts of headings, code blocks, callouts, images, and tables match. Check the front matter is complete. Check every fenced block survived. Then build and open the PR as `SKILL.md` describes. diff --git a/.agents/skills/translate/side-by-side.js b/.agents/skills/translate/side-by-side.js new file mode 100644 index 000000000000..a5626c83d218 --- /dev/null +++ b/.agents/skills/translate/side-by-side.js @@ -0,0 +1,274 @@ +#!/usr/bin/env node +// Lines up an English page with its translation, block by block. +// +// node .agents/skills/translate/side-by-side.js es docs/get-started/*.md > review.html +// node .agents/skills/translate/side-by-side.js es docs/build/triggers.md --base origin/i18n > review.html +// node .agents/skills/translate/side-by-side.js es docs/build/triggers.md --json +// node .agents/skills/translate/side-by-side.js es docs/get-started/*.md --check --base HEAD +// +// The HTML pairs each block of the English the page was translated from (its +// translation_source_hash) with the translated block. --base highlights +// the blocks whose English changed since the translation at , to review +// an update. +// +// --json lists the blocks of the current English, each with the translation +// that can be reused for it, or null where the English is new or changed. See +// "Updating a page" in pages.md. +// +// --check lists blocks where a fixed glossary term appears fewer times in the +// translation than in the English, which can mean it was translated. With +// --base, only blocks whose English changed since are checked, so a +// difference someone has already looked at is not raised again. +// +// A block is a run of lines between blank lines, after formatting with +// Prettier, and a fenced code block is one block. Both sides go through +// Prettier so wrapping differences don't count as changes. +const fs = require('fs'); +const { execFileSync } = require('child_process'); +const prettier = require('prettier'); +const yaml = require('js-yaml'); // installed with Docusaurus + +const FENCE_OPEN = ''; +const FENCE_CLOSE = ''; + +// Terms that must stay in English. Product nouns like "run" are left out, +// since their ordinary-English uses get translated. +const FIXED = yaml + .load(fs.readFileSync('glossary.yml', 'utf8')) + .terms.filter(t => !t.translate && !t.product_noun) + .map(t => ({ + term: t.term, + re: new RegExp( + `\\b${t.term.replace(/ /g, '\\s+')}s?\\b`, + t.case_sensitive ? 'g' : 'gi' + ), + })); + +// Heading anchors and link targets are copied unchanged, so count only prose. +const prose = s => s.replace(/\{#[^}]*\}/g, '').replace(/\]\([^)]*\)/g, ']'); + +// A higher count in the translation is harmless; a lower one is worth a look. +const termDrops = (english, translation) => + FIXED.map(({ term, re }) => ({ + term, + en: (prose(english).match(re) || []).length, + es: (prose(translation).match(re) || []).length, + })).filter(c => c.es < c.en); + +const git = (...args) => + execFileSync('git', args, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + +function splitFrontMatter(text) { + const m = text.match(/^---\n([\s\S]*?)\n---\n/); + return m + ? { fm: m[1], body: text.slice(m[0].length) } + : { fm: '', body: text }; +} + +const field = (fm, key) => + (fm.match(new RegExp(`^${key}: *(.*)$`, 'm')) || [])[1]; + +// Blocks inside do-not-retranslate fences are flagged, and the markers dropped. +async function blocks(text, filepath) { + const options = { + ...(await prettier.resolveConfig(filepath)), + parser: 'markdown', + }; + const formatted = await prettier.format(splitFrontMatter(text).body, options); + const out = []; + let cur = []; + let inCode = false; + let fenced = false; + const flush = () => { + if (cur.length) out.push({ text: cur.join('\n'), fenced }); + cur = []; + }; + for (const line of formatted.split('\n')) { + if (!inCode && line.trim() === FENCE_OPEN) { + flush(); + fenced = true; + continue; + } + if (!inCode && line.trim() === FENCE_CLOSE) { + flush(); + fenced = false; + continue; + } + if (/^\s*(```|~~~)/.test(line)) inCode = !inCode; + if (!inCode && line.trim() === '') flush(); + else cur.push(line); + } + flush(); + return out; +} + +// The English the translation at esPath (or esText) was made from, as blocks. +async function sourceOf(esText, enPath) { + const hash = field(splitFrontMatter(esText).fm, 'translation_source_hash'); + if (!hash) + return { hash, error: 'No translation_source_hash in the front matter.' }; + try { + return { hash, blocks: await blocks(git('cat-file', '-p', hash), enPath) }; + } catch { + return { + hash, + error: `The English it was translated from (${hash}) is not in the repo.`, + }; + } +} + +async function page(locale, enPath, base) { + const rel = enPath.replace(/^docs\//, ''); + const esPath = `i18n/${locale}/docusaurus-plugin-content-docs/current/${rel}`; + const result = { page: rel, notes: [] }; + if (!fs.existsSync(esPath)) + return { ...result, error: 'No translation yet.' }; + + const esText = fs.readFileSync(esPath, 'utf8'); + const source = await sourceOf(esText, enPath); + if (source.error) return { ...result, error: source.error }; + const esBlocks = await blocks(esText, esPath); + const current = await blocks(fs.readFileSync(enPath, 'utf8'), enPath); + + result.aligned = source.blocks.length === esBlocks.length; + if (!result.aligned) + result.notes.push( + `The blocks don't line up: ${source.blocks.length} in the English, ${esBlocks.length} in the translation.` + ); + if (source.hash !== git('hash-object', enPath).trim()) + result.notes.push('The English has changed since this was translated.'); + + // Translation to reuse for each English block, keyed by the block's text. + const reuse = new Map(); + if (result.aligned) + source.blocks.forEach((b, i) => reuse.set(b.text, { ...esBlocks[i], i })); + const used = new Set(); + result.blocks = current.map(b => { + const t = reuse.get(b.text); + if (t) used.add(t.i); + return { + english: b.text, + translation: t ? t.text : null, + fenced: t ? t.fenced : false, + }; + }); + result.unusedFenced = esBlocks + .filter((b, i) => b.fenced && !used.has(i)) + .map(b => b.text); + + // For the HTML view: the source English next to the translation. + result.rows = source.blocks.map((b, i) => ({ + english: b.text, + translation: esBlocks[i]?.text ?? '', + })); + for (const b of esBlocks.slice(source.blocks.length)) + result.rows.push({ english: '', translation: b.text }); + + if (base) { + let before; + try { + before = git('show', `${base}:${esPath}`); + } catch { + result.notes.push(`New translation since ${base}.`); + } + if (before) { + const old = await sourceOf(before, enPath); + const oldTexts = new Set((old.blocks || []).map(b => b.text)); + for (const row of result.rows) + row.changed = row.english !== '' && !oldTexts.has(row.english); + } + } + + // Compare block by block where the blocks line up, otherwise the whole page. + const toCheck = result.aligned + ? result.rows.filter(r => r.changed !== false) + : [ + { + english: source.blocks.map(b => b.text).join('\n\n'), + translation: esBlocks.map(b => b.text).join('\n\n'), + }, + ]; + result.termDrops = toCheck + .map(r => ({ ...r, drops: termDrops(r.english, r.translation) })) + .filter(r => r.drops.length); + return result; +} + +const esc = s => + s.replace(/&/g, '&').replace(//g, '>'); + +function html(pages, locale) { + const sections = pages.map(p => { + const notes = [p.error, ...(p.notes || [])] + .filter(Boolean) + .map(n => `

${esc(n)}

`) + .join(''); + const rows = (p.rows || []) + .map( + r => + `${esc(r.english)}${esc(r.translation)}` + ) + .join('\n'); + return `

${esc(p.page)}

${notes}${rows ? `\n${rows}
English${esc(locale)}
` : ''}`; + }); + return ` +Translation review + +

Translation review

+

Each row is one block of the English the page was translated from, next to its translation. Highlighted rows changed since the base.

+${sections.join('\n')} + +`; +} + +async function main() { + const args = process.argv.slice(2); + const json = args.includes('--json'); + const check = args.includes('--check'); + const baseAt = args.indexOf('--base'); + const base = baseAt === -1 ? null : args[baseAt + 1]; + const [locale, ...files] = args.filter( + (a, i) => !a.startsWith('--') && (baseAt === -1 || i !== baseAt + 1) + ); + if (!locale || !files.length) { + console.error( + 'Usage: side-by-side.js ... [--base ] [--json | --check]' + ); + process.exit(1); + } + const pages = []; + for (const f of files) pages.push(await page(locale, f, base)); + if (check) { + const flat = s => s.replace(/\s+/g, ' '); + let found = 0; + for (const p of pages) { + if (p.error) console.log(`${p.page}: ${p.error}`); + for (const r of p.termDrops || []) { + found++; + const counts = r.drops.map( + d => `"${d.term}" ${d.en} in English, ${d.es} in translation` + ); + console.log( + `${p.page}: ${counts.join('; ')}\n en: ${flat(r.english)}\n ${locale}: ${flat(r.translation)}\n` + ); + } + } + if (!found) console.log('No glossary terms missing.'); + } else if (json) { + const out = pages.map(({ rows, termDrops, ...p }) => p); + console.log(JSON.stringify(out, null, 2)); + } else { + process.stdout.write(html(pages, locale)); + } +} + +main(); From 3c97a9fe092e8b0d85e15d040ee2807a5e9cb012 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 14:30:09 +0100 Subject: [PATCH 15/34] Show the old translation and a word diff for changed blocks side-by-side.js --json now gives each changed block a previous field with the old English, its old translation and a word diff, so the translator can tell a typo fix from a change in meaning. pages.md says how to use it, and that the source hash is updated even when no translated text changes. --- .agents/skills/translate/pages.md | 30 ++++++++-- .agents/skills/translate/side-by-side.js | 71 +++++++++++++++++++++++- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 9dfb7baaf430..828a70b80e38 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -65,11 +65,28 @@ no longer see what did. So translate only the blocks whose English changed: node .agents/skills/translate/side-by-side.js docs/.md --json ``` -This lists the blocks of the current English. Each has the existing -`translation` to reuse, or `null` where the English is new or changed. Write the -page as those blocks in order, separated by blank lines: copy each reused -translation exactly, and translate each `null` block. Then update the front -matter and run Prettier as usual. +This prints a JSON array with one entry per page. Its `blocks` are the blocks of +the current English. Each has the existing `translation` to reuse, or `null` +where the English is new or changed. Write the page as those blocks in order, +separated by blank lines: copy each reused translation exactly, and work out +each `null` block. Then update the front matter and run Prettier as usual. + +A changed block usually has a `previous` field: the old English, its old +translation, and a word `diff` between the old and new English, marked +`[-removed-]` and `{+added+}`. Use the diff to decide: + +- If only links, inline code, or heading anchors changed, keep the old + translation and copy those changes into it. +- If the prose changed but the old translation already says what the new English + says, keep it as it is. A typo fix in the English often needs nothing. +- Otherwise, translate the block. Reuse the old wording where it still fits, so + the reviewer sees only what changed. + +A block with no `previous` is new, or could not be paired with an old block. +Translate it. + +Update `translation_source_hash` even if no translated text changed. It records +that the translation was checked against this English. - A block with `fenced: true` was inside a `` fence. Put the fence back around it. If `unusedFenced` is not empty, the English a @@ -79,7 +96,8 @@ matter and run Prettier as usual. cannot be matched to its English. Translate the whole page again, keeping fenced blocks, and say so in the PR. -In the PR description, list for each page how many blocks were translated. +In the PR description, list for each page how many blocks changed and how many +of those needed new translated text. ## Reviewing diff --git a/.agents/skills/translate/side-by-side.js b/.agents/skills/translate/side-by-side.js index a5626c83d218..6804047345c2 100644 --- a/.agents/skills/translate/side-by-side.js +++ b/.agents/skills/translate/side-by-side.js @@ -12,8 +12,9 @@ // an update. // // --json lists the blocks of the current English, each with the translation -// that can be reused for it, or null where the English is new or changed. See -// "Updating a page" in pages.md. +// that can be reused for it, or null where the English is new or changed. A +// changed block also gets the old English and translation it replaced, with a +// word diff, under previous. See "Updating a page" in pages.md. // // --check lists blocks where a fixed glossary term appears fewer times in the // translation than in the English, which can mean it was translated. With @@ -55,6 +56,38 @@ const termDrops = (english, translation) => es: (prose(translation).match(re) || []).length, })).filter(c => c.es < c.en); +// Word diff in git's --word-diff=plain style: [-removed-]{+added+}. +function wordDiff(a, b) { + const x = a.split(/\s+/); + const y = b.split(/\s+/); + const lcs = x.map(() => Array(y.length + 1).fill(0)); + lcs.push(Array(y.length + 1).fill(0)); + for (let i = x.length - 1; i >= 0; i--) + for (let j = y.length - 1; j >= 0; j--) + lcs[i][j] = + x[i] === y[j] + ? lcs[i + 1][j + 1] + 1 + : Math.max(lcs[i + 1][j], lcs[i][j + 1]); + const out = []; + let i = 0; + let j = 0; + while (i < x.length || j < y.length) { + if (i < x.length && j < y.length && x[i] === y[j]) { + out.push(x[i++]); + j++; + } else if ( + i < x.length && + (j === y.length || lcs[i + 1][j] >= lcs[i][j + 1]) + ) + out.push(`[-${x[i++]}-]`); + else out.push(`{+${y[j++]}+}`); + } + return out + .join(' ') + .replace(/-\] \[-/g, ' ') + .replace(/\+\} \{\+/g, ' '); +} + const git = (...args) => execFileSync('git', args, { encoding: 'utf8', @@ -155,6 +188,40 @@ async function page(locale, enPath, base) { fenced: t ? t.fenced : false, }; }); + // Pair each changed block with the old block it replaced: a run of changed + // blocks between two reused ones takes the old blocks in the same gap, when + // the counts match. Otherwise the block is new, or the pairing is unclear. + if (result.aligned) { + // Repeated blocks like ":::" match in order, so the next unused one. + let last = -1; + const at = current.map(b => { + const i = source.blocks.findIndex( + (s, k) => k > last && s.text === b.text + ); + if (i !== -1) last = i; + return i === -1 ? undefined : i; + }); + for (let i = 0; i < current.length;) { + if (at[i] !== undefined) { + i++; + continue; + } + let j = i; + while (j < current.length && at[j] === undefined) j++; + const from = i === 0 ? 0 : at[i - 1] + 1; + const to = j === current.length ? source.blocks.length : at[j]; + if (to - from === j - i) + for (let k = 0; k < j - i; k++) { + const old = source.blocks[from + k].text; + result.blocks[i + k].previous = { + english: old, + translation: esBlocks[from + k].text, + diff: wordDiff(old, current[i + k].text), + }; + } + i = j; + } + } result.unusedFenced = esBlocks .filter((b, i) => b.fenced && !used.has(i)) .map(b => b.text); From 62e87fea662387e563d78151bd5b31b68d402c0c Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 15:59:06 +0100 Subject: [PATCH 16/34] Show review blocks stacked, English above the translation --- .agents/skills/translate/side-by-side.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.agents/skills/translate/side-by-side.js b/.agents/skills/translate/side-by-side.js index 6804047345c2..c069efe78ea4 100644 --- a/.agents/skills/translate/side-by-side.js +++ b/.agents/skills/translate/side-by-side.js @@ -267,7 +267,7 @@ async function page(locale, enPath, base) { const esc = s => s.replace(/&/g, '&').replace(//g, '>'); -function html(pages, locale) { +function html(pages) { const sections = pages.map(p => { const notes = [p.error, ...(p.notes || [])] .filter(Boolean) @@ -276,22 +276,24 @@ function html(pages, locale) { const rows = (p.rows || []) .map( r => - `${esc(r.english)}${esc(r.translation)}` + `
${esc(r.english)}
${esc(r.translation)}
` ) .join('\n'); - return `

${esc(p.page)}

${notes}${rows ? `\n${rows}
English${esc(locale)}
` : ''}`; + return `

${esc(p.page)}

${notes}${rows}`; }); return ` Translation review

Translation review

-

Each row is one block of the English the page was translated from, next to its translation. Highlighted rows changed since the base.

+

Each box is one block of the English the page was translated from (grey, on top), with its translation underneath. Boxes with a yellow border changed since the base.

${sections.join('\n')} `; @@ -334,7 +336,7 @@ async function main() { const out = pages.map(({ rows, termDrops, ...p }) => p); console.log(JSON.stringify(out, null, 2)); } else { - process.stdout.write(html(pages, locale)); + process.stdout.write(html(pages)); } } From 5364ef8f8d83922faddc6f01593d193e924e28b2 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 15:59:06 +0100 Subject: [PATCH 17/34] Give each locale a house style guide for translation Move the Spanish register and gender rules from translation-rules.yml into .agents/skills/translate/es.md, and add rules from the Get Started review: Spanish capitalization, straight quotes, "por ejemplo" instead of "p. ej.", and more es-419 word choices. translation-rules.yml now holds only single-phrase fixes from review. The skill also asks for natural rather than word-for-word phrasing, and retranslates machine pages when the house style changes. --- .agents/skills/translate/SKILL.md | 16 +++++--- .agents/skills/translate/es.md | 59 ++++++++++++++++++++++++++++ .agents/skills/translate/pages.md | 7 ++-- AGENTS.md | 6 +-- translation-rules.yml | 65 ++++++++++++++++++------------- 5 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 .agents/skills/translate/es.md diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index b55ae2038805..4ce844683a63 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -4,8 +4,9 @@ description: Translates the docs under i18n/ into each language enabled in docusaurus.config.js, either a set of English pages or the interface text (navbar, footer, sidebar headings, homepage). Respects glossary.yml, - translation-rules.yml, review status, and do-not-retranslate fences, and opens - one PR per locale. Use when asked to translate or refresh translations. + translation-rules.yml, each locale's house style, review status, and + do-not-retranslate fences, and opens one PR per locale. Use when asked to + translate or refresh translations. disable-model-invocation: true --- @@ -35,12 +36,13 @@ or articles and blog posts. ## Before you start -Check these three things. If any fails, stop and ask. +Check these four things. If any fails, stop and ask. - The locale is enabled in `docusaurus.config.js`. Do not enable it yourself; that changes what gets deployed. - `i18n/` is not in `.gitignore`. - `glossary.yml` and `translation-rules.yml` are valid YAML. +- The locale has a house style guide, `.md`, in this folder. ## How to translate @@ -49,8 +51,12 @@ These apply to both tasks. - Words in `glossary.yml` stay in English. For ordinary words that are also product terms, like "run" or "step", keep the English only when the word means the OpenFn thing. -- Follow any rules for the locale in `translation-rules.yml`. By default, - Spanish uses "tú". +- Follow the house style for the locale in `.md` in this folder, such as + `es.md`, and any rules for the locale in `translation-rules.yml`. +- Write the way a native writer would, not word for word. Reorder or split a + sentence when the literal version is awkward, drop a subject the sentence has + already given, and cut an aside that repeats what the sentence says. Keep the + meaning and the facts; change only the wording. - Copy code blocks and inline code exactly. You may translate comments inside code. - Keep the names of things in the app, like buttons, menus, tabs, and field diff --git a/.agents/skills/translate/es.md b/.agents/skills/translate/es.md new file mode 100644 index 000000000000..8996a1ae1b6e --- /dev/null +++ b/.agents/skills/translate/es.md @@ -0,0 +1,59 @@ +# Spanish house style + +Humans maintain this file. Read it before translating into Spanish (`es`). + +## Variety and voice + +Write neutral Latin American Spanish (es-419), for readers across the region. +Avoid slang from any one country. + +- Address the reader as "tú", never "usted", "vosotros", or "vos". A verb with + no subject can read as "usted" ("podría crear"), so use the "tú" form + ("podrías crear"). +- When an English example switches to "I" or "we", keep it addressed to the + reader. + +| Use | Not | +| ---------------------------------- | --------------------- | +| computadora | ordenador | +| capacitación (training) | formación | +| lista de verificación (checklist) | lista de comprobación | +| contactar a | contactar con | +| reportar (a problem) | informar de | +| recolección de datos | recogida de datos | +| confiable | fiable | +| actualmente (currently) | ahora mismo | +| considerar | plantearse | +| por ejemplo, or como before a list | p. ej. | + +## English terms kept in Spanish + +Terms from `glossary.yml` that stay in English take a fixed gender and an +English plural with "-s". + +- Feminine: la work order, la CLI, la API, la iPaaS, la pull request. +- Masculine: el workflow, el step, el job, el run, el trigger, el adaptor, el + webhook, el dataclip, el sandbox, el Canvas, el Inspector, el cron. +- Any English term not listed is masculine. + +For example, "The work order creates two runs" becomes "La work order crea dos +runs". + +## Capitals + +Follow Spanish capitalization, not the English. Spanish capitalizes far less, +and copying the English makes the page read as a translation. + +- In titles and headings, capitalize only the first word and proper names. +- Capitalize proper names: product and organization names, named programs and + publications, case-sensitive glossary terms (OpenFn, Lightning, Canvas, + Inspector, CLI), and labels copied from the app. +- Write everything else in lower case, including kept English terms mid-sentence + and categories the English capitalizes. "A Project contains Workflows, + Triggers, and Credentials" becomes "Un proyecto contiene workflows, triggers y + credenciales". "Digital Public Good" becomes "bien público digital". + +## Punctuation + +- Use straight double quotes ("...") for every quotation, even where the English + uses curly quotes. Do not use curly quotes or comillas angulares («...»). diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 828a70b80e38..5918226e091d 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -41,9 +41,10 @@ also add `translation_reviewer` and `translation_review_date`. - **No translation yet.** Translate the whole page. - **The hash matches the current English file.** Skip it, whatever its status. The English has not changed since it was translated. The one exception: if - `glossary.yml` or `translation-rules.yml` was committed more recently than the - translation (compare `git log -1 --format=%ct -- `), translate a - `machine` page again in full, so it picks up the new rules. + `glossary.yml`, `translation-rules.yml`, or the locale's house style + (`.md`) was committed more recently than the translation (compare + `git log -1 --format=%ct -- `), translate a `machine` page again in + full, so it picks up the new rules. - **The hash no longer matches, and the status is `machine`, `needs-review`, or missing.** Translate only what changed (see "Updating a page" below). - **The hash no longer matches, and the status is `human-reviewed`.** Leave the diff --git a/AGENTS.md b/AGENTS.md index 7ee6d7d1e5da..71ed052d355d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,9 +31,9 @@ you need. **Special rules apply** - Translations in `i18n/`. See `translate/SKILL.md`. -- The two rule files: `glossary.yml` and `translation-rules.yml`. Humans - maintain these. Each explains its format at the top. Only add an entry if the - user asks you to. +- The rule files: `glossary.yml`, `translation-rules.yml`, and the house style + for each locale in `.agents/skills/translate/.md`. Humans maintain + these. Only add an entry if the user asks you to. To check facts, you can read the product code. Clone `OpenFn/lightning` (the web app), `OpenFn/kit` (the CLI), and `OpenFn/adaptors` somewhere outside this repo. diff --git a/translation-rules.yml b/translation-rules.yml index 195e8f8c4cd7..1aa67b7c0250 100644 --- a/translation-rules.yml +++ b/translation-rules.yml @@ -2,13 +2,14 @@ # # Purpose # ------- -# Locale-specific phrasing rules learned from human edits to machine -# translations. The translate skill (.agents/skills/translate/SKILL.md) loads this -# file after glossary.yml and applies every rule whose `locale` matches the -# target locale. +# Fixes learned from human review of machine translations: a phrase that must +# always be translated one way, or a rendering a reviewer rejected. The +# translate skill (.agents/skills/translate/SKILL.md) loads this file after +# glossary.yml and applies every rule whose `locale` matches the target locale. # -# Glossary terms (never translate) belong in glossary.yml, not here. This file -# is for how to translate, not what to leave alone. +# Glossary terms (never translate) belong in glossary.yml. A locale's general +# style (voice, word choice, capitals, punctuation) belongs in its house style +# guide, .agents/skills/translate/.md. This file is for single phrases. # # Humans maintain this file. Add a rule when you correct a translation in a # way that should apply to other pages too. @@ -18,13 +19,9 @@ # rules: # - locale: string # "es" or "fr" (or "*" for every locale) # kind: string # one of: -# # term - a fixed rendering for a phrase -# # register - tone/voice guidance (formal vs informal "you") -# # punctuation - spacing, quotation marks, list punctuation -# # structure - how to handle headings, admonition titles, UI labels -# # avoid - a rendering the reviewer rejected -# source: string # English phrase or pattern the rule applies to (for -# # kind: term and avoid). Omit for global rules. +# # term - a fixed rendering for a phrase +# # avoid - a rendering the reviewer rejected +# source: string # English phrase the rule applies to # target: string # required rendering (kind: term) or rejected rendering # # (kind: avoid) # instruction: string # plain-language rule the translator must follow @@ -39,20 +36,36 @@ # ------- # rules: # - locale: es -# kind: register -# instruction: Address the reader as "tú", not "usted". -# reason: Matches the informal tone of the English docs. -# added_by: someone -# added_on: 2026-01-01 -# source_pr: https://github.com/OpenFn/docs/pull/000 -# - locale: fr -# kind: punctuation -# instruction: Put a non-breaking space before ":", ";", "?" and "!". -# example_source: "Next step: add a credential." -# example_target: "Étape suivante : ajoutez un identifiant." -# reason: Standard French typography. +# kind: avoid +# source: report (a problem) +# target: informar de +# instruction: Use "reportar", not "informar de". +# reason: "Informar de" is Spain-only; the Spanish docs target es-419. # added_by: someone # added_on: 2026-01-01 # source_pr: https://github.com/OpenFn/docs/pull/000 -rules: [] +rules: + - locale: es + kind: term + source: deliverables + target: entregables + instruction: >- + In project plans, "deliverables" are "entregables", not "resultados". + example_source: "Key deliverables:" + example_target: "Entregables clave:" + reason: The standard project-management term in Latin America. + added_by: lmac-1 + added_on: 2026-10-01 + - locale: es + kind: term + source: upgrade (a plan) + target: mejorar + instruction: >- + "Upgrade" a plan or account is "mejorar", not "actualizar", which means + "update". + example_source: Upgrade to access more features. + example_target: Mejora tu plan para acceder a más funciones. + reason: '"Actualiza tu plan" reads as "update your plan".' + added_by: lmac-1 + added_on: 2026-10-01 From 6f78f9d4b1abf07405095dde7021eed7196b0538 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 16:51:58 +0100 Subject: [PATCH 18/34] Drop the entregables translation rule The English says "Key Outputs", not "deliverables", so the rule never matched and "Resultados clave" was already right. --- translation-rules.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/translation-rules.yml b/translation-rules.yml index 1aa67b7c0250..95d78bd673cc 100644 --- a/translation-rules.yml +++ b/translation-rules.yml @@ -45,17 +45,6 @@ # added_on: 2026-01-01 # source_pr: https://github.com/OpenFn/docs/pull/000 -rules: - - locale: es - kind: term - source: deliverables - target: entregables - instruction: >- - In project plans, "deliverables" are "entregables", not "resultados". - example_source: "Key deliverables:" - example_target: "Entregables clave:" - reason: The standard project-management term in Latin America. - added_by: lmac-1 added_on: 2026-10-01 - locale: es kind: term From 4d2610f7157844223274c0967bec415b1def09b6 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 16:52:14 +0100 Subject: [PATCH 19/34] Restore the rules key in translation-rules.yml The previous commit deleted one line too early. --- translation-rules.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translation-rules.yml b/translation-rules.yml index 95d78bd673cc..d8f36840b1ad 100644 --- a/translation-rules.yml +++ b/translation-rules.yml @@ -45,7 +45,7 @@ # added_on: 2026-01-01 # source_pr: https://github.com/OpenFn/docs/pull/000 - added_on: 2026-10-01 +rules: - locale: es kind: term source: upgrade (a plan) From ba47041c2723e4263ca55e991ac511cff8bbb819 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 17:00:48 +0100 Subject: [PATCH 20/34] Apply new translation rules without retranslating whole pages A change to the glossary, the rules, or a locale's house style used to retranslate every machine page in full, which rewords every block and buries what the rule changed. Now only the text that breaks a new rule is fixed. Deleting a page still gives a fresh translation when that is wanted. --- .agents/skills/translate/pages.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 5918226e091d..e8a0021abb2e 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -42,9 +42,9 @@ also add `translation_reviewer` and `translation_review_date`. - **The hash matches the current English file.** Skip it, whatever its status. The English has not changed since it was translated. The one exception: if `glossary.yml`, `translation-rules.yml`, or the locale's house style - (`.md`) was committed more recently than the translation (compare - `git log -1 --format=%ct -- `), translate a `machine` page again in - full, so it picks up the new rules. + (`.md`) was committed more recently than a `machine` or + `needs-review` page (compare `git log -1 --format=%ct -- `), apply the + new rules to it (see "When the rules change" below). - **The hash no longer matches, and the status is `machine`, `needs-review`, or missing.** Translate only what changed (see "Updating a page" below). - **The hash no longer matches, and the status is `human-reviewed`.** Leave the @@ -57,6 +57,29 @@ also add `translation_reviewer` and `translation_review_date`. no longer in the repo, say so and offer a full retranslation in that PR instead. +## When the rules change + +Do not retranslate the page. A full retranslation rewords every block, and a +reviewer can no longer see what the new rules changed. + +1. See what changed in the rules since the page was last committed: + + ```bash + git diff $(git log -1 --format=%H -- ) -- glossary.yml translation-rules.yml .agents/skills/translate/.md + ``` + +2. Fix only the text that breaks a rule that was added or changed. Leave + everything else, even wording you would now write differently. +3. Leave fenced blocks as they are. If one breaks a new rule, say so in the PR. + +If nothing breaks the new rules, there is nothing to commit, and the page is +checked again on the next run. That is quick, because only the changed rules +are checked. + +To retranslate a page from scratch on purpose, for example while tuning the +rules on a first section, delete it first. It then counts as having no +translation. + ## Updating a page Retranslating a whole page rewords text that has not changed, and a reviewer can From 4795435d93d7618c7848f6f9340f02061eda1a42 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 17:15:42 +0100 Subject: [PATCH 21/34] Add a review-translation skill Checks translated pages against the English in a fresh session, after /translate pages: searches for house style rule breaks, reads each block for changed meaning and literal phrasing, fixes what is clear, and reports the rest. It also says what marking a page human-reviewed involves. Also formats the "When the rules change" section of pages.md, which missed Prettier. --- .agents/skills/review-translation/SKILL.md | 112 +++++++++++++++++++++ .agents/skills/translate/pages.md | 13 ++- AGENTS.md | 3 + 3 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 .agents/skills/review-translation/SKILL.md diff --git a/.agents/skills/review-translation/SKILL.md b/.agents/skills/review-translation/SKILL.md new file mode 100644 index 000000000000..bb26e2b6dc7c --- /dev/null +++ b/.agents/skills/review-translation/SKILL.md @@ -0,0 +1,112 @@ +--- +name: review-translation +description: + Checks translated docs pages against the English in a fresh session, fixes + clear problems (changed meaning, literal phrasing, broken house style rules), + and reports the rest. Does not commit or open a PR. Use after /translate + pages, before committing a section. +disable-model-invocation: true +--- + +# Review a translation + +Run `/review-translation ` in a fresh session, not the one that +translated the pages: a translator is poor at spotting its own mistakes. The +scope is the same as for `/translate pages`: one page, one folder under `docs/`, +or one sidebar category. If no scope is given, ask. + +The review checks against the rules the translator followed: "How to translate" +in `.agents/skills/translate/SKILL.md`, the house style in +`.agents/skills/translate/.md`, the rules for the locale in +`translation-rules.yml`, and `glossary.yml`. + +The review fixes clear problems in `machine` and `needs-review` pages and +reports the rest. It does not commit, open a PR, or add rules. Never edit a +`human-reviewed` page or a fenced block; report the problem instead. + +## 1. Lay out the pages + +Get each block of English next to its translation: + +```bash +node .agents/skills/translate/side-by-side.js docs/.md... --json +``` + +Then read the rules listed above. + +## 2. Search for rule breaks + +These need no judgement, so search for them across the whole scope rather than +reading for them: + +- Run the glossary check from "Check each page" in + `.agents/skills/translate/pages.md`. +- Every word or phrase `.md` says not to use, such as the "Not" column + of a word table. +- Punctuation `.md` rules out, such as curly quotes. +- English terms kept from `glossary.yml` written with a capital mid-sentence, if + `.md` says to lower-case them. Case-sensitive terms (OpenFn, Canvas, + Inspector) and names of things in the app keep their capitals. +- Every `term` and `avoid` rule for the locale in `translation-rules.yml`. + +## 3. Read each block against its English + +Check that the translation: + +- **Says the same thing.** Nothing added, dropped, or softened. Numbers, dates, + limits, and "must", "should", and "can" match, and so does who does what. +- **Reads naturally.** No word-for-word phrasing, repeated subjects, stacked + nouns, or asides that repeat the sentence. +- **Uses the right voice**, as `.md` sets it. In Spanish, watch for verb + forms that read as "usted". +- **Keeps names of things in the app** exactly as in the English, and keeps + links and inline code the same. +- **Is consistent across the scope.** The same English term gets the same + translation on every page. + +Do not flag wording you would only have written differently. If the English +itself is wrong or unclear, note it under "Problems in the English" and leave +the translation matching it. + +## 4. Fix what is clear + +Fix a block when the problem and the fix are both clear. Change only the words +that are wrong, so the diff shows just the fix. Leave the front matter alone. +Then run `yarn prettier --write ` on the pages you changed, and run the +glossary check again. + +## 5. Report + +Report in the chat, grouped like this, with the file and line for each item: + +- **Fixed.** Before and after, and why, one line each. +- **Needs a decision.** Problems you were not sure how to fix, or where the fix + changes meaning. +- **Problems in the English.** +- **Suggested rules.** A problem found on more than one page, or likely to come + back in later sections, with the line you would add to `.md` or + `translation-rules.yml`. Add it only if asked. +- **Page to spot-check.** The page with the most fixes, and the command to open + it side by side: + + ```bash + node .agents/skills/translate/side-by-side.js docs/.md > review.html + ``` + +## Marking a page human-reviewed + +Only a person who reads the language well marks a page `human-reviewed`. It +means they checked the meaning against the English, not only that the +translation reads well: a page can read perfectly and still say something +different. Using `review.html`, they check every block for the points in step 3, +fix what is wrong, and then set in the front matter: + +```yaml +translation_review_status: human-reviewed +translation_reviewer: +translation_review_date: +``` + +A fix that would apply to other pages goes into `.md` or +`translation-rules.yml` too. A `human-reviewed` page is never retranslated in +full again; changes to its English come to the reviewer as a separate PR. diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index e8a0021abb2e..5f4b3bb5db12 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -42,9 +42,9 @@ also add `translation_reviewer` and `translation_review_date`. - **The hash matches the current English file.** Skip it, whatever its status. The English has not changed since it was translated. The one exception: if `glossary.yml`, `translation-rules.yml`, or the locale's house style - (`.md`) was committed more recently than a `machine` or - `needs-review` page (compare `git log -1 --format=%ct -- `), apply the - new rules to it (see "When the rules change" below). + (`.md`) was committed more recently than a `machine` or `needs-review` + page (compare `git log -1 --format=%ct -- `), apply the new rules to it + (see "When the rules change" below). - **The hash no longer matches, and the status is `machine`, `needs-review`, or missing.** Translate only what changed (see "Updating a page" below). - **The hash no longer matches, and the status is `human-reviewed`.** Leave the @@ -73,8 +73,8 @@ reviewer can no longer see what the new rules changed. 3. Leave fenced blocks as they are. If one breaks a new rule, say so in the PR. If nothing breaks the new rules, there is nothing to commit, and the page is -checked again on the next run. That is quick, because only the changed rules -are checked. +checked again on the next run. That is quick, because only the changed rules are +checked. To retranslate a page from scratch on purpose, for example while tuning the rules on a first section, delete it first. It then counts as having no @@ -135,6 +135,9 @@ Add `--base `, such as `--base origin/i18n`, to highlight the blocks that changed since the translation at that ref. Put both commands in the PR description, with the page paths filled in. +To check a translation against the English and fix it, run `/review-translation` +in a fresh session. + ## Fenced blocks A human can wrap part of a translation like this: diff --git a/AGENTS.md b/AGENTS.md index 71ed052d355d..75043d830659 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,6 +50,9 @@ Never change them. that to identify-gaps. Suited to a monthly schedule. - **`translate`** translates English pages (`/translate pages`) or the interface text (`/translate interface`), in its own PR per locale. +- **`review-translation`** checks translated pages against the English, fixes + clear problems, and reports the rest. Run it in a fresh session after + `/translate pages`, before committing. `update-content` and `translate` open PRs, so they only run when someone asks for them by name (`/update-content`, `/translate`). When another skill hands off From 3d453e03b5a5023537d7303050e5ebd290670644 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 17:25:06 +0100 Subject: [PATCH 22/34] Check that the branch has the latest main before translating --- .agents/skills/translate/SKILL.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index 4ce844683a63..99cbeefb6d80 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -36,13 +36,18 @@ or articles and blog posts. ## Before you start -Check these four things. If any fails, stop and ask. +Check these five things. If any fails, stop and ask. - The locale is enabled in `docusaurus.config.js`. Do not enable it yourself; that changes what gets deployed. - `i18n/` is not in `.gitignore`. - `glossary.yml` and `translation-rules.yml` are valid YAML. - The locale has a house style guide, `.md`, in this folder. +- Your branch has everything on `main`. Run `git fetch origin main` and then + `git merge-base --is-ancestor origin/main HEAD`. If it fails, the English you + would translate is out of date, and the hashes you record will not match + `main`. Ask to merge `main` in first. If your branch comes off another branch, + merge `main` into that one, then that one into yours. ## How to translate From 4c3fcc51f4297df6c7a2a28f76e849f8c6165243 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Thu, 1 Oct 2026 18:44:40 +0100 Subject: [PATCH 23/34] Commit a translation before reviewing it, and check every block --- .agents/skills/review-translation/SKILL.md | 14 ++++++++++---- AGENTS.md | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.agents/skills/review-translation/SKILL.md b/.agents/skills/review-translation/SKILL.md index bb26e2b6dc7c..538efe8e5ea5 100644 --- a/.agents/skills/review-translation/SKILL.md +++ b/.agents/skills/review-translation/SKILL.md @@ -4,14 +4,15 @@ description: Checks translated docs pages against the English in a fresh session, fixes clear problems (changed meaning, literal phrasing, broken house style rules), and reports the rest. Does not commit or open a PR. Use after /translate - pages, before committing a section. + pages, once the section is committed. disable-model-invocation: true --- # Review a translation Run `/review-translation ` in a fresh session, not the one that -translated the pages: a translator is poor at spotting its own mistakes. The +translated the pages: a translator is poor at spotting its own mistakes. Commit +the translation first, so the review's fixes show up as their own diff. The scope is the same as for `/translate pages`: one page, one folder under `docs/`, or one sidebar category. If no scope is given, ask. @@ -39,8 +40,13 @@ Then read the rules listed above. These need no judgement, so search for them across the whole scope rather than reading for them: -- Run the glossary check from "Check each page" in - `.agents/skills/translate/pages.md`. +- Run the glossary check on every block. Leave out `--base`: the translation is + already committed, so `--base HEAD` would skip every block. + + ```bash + node .agents/skills/translate/side-by-side.js docs/.md... --check + ``` + - Every word or phrase `.md` says not to use, such as the "Not" column of a word table. - Punctuation `.md` rules out, such as curly quotes. diff --git a/AGENTS.md b/AGENTS.md index 4078e00b4e8f..b24affced83e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,8 +51,8 @@ Never change them. - **`translate`** translates English pages (`/translate pages`) or the interface text (`/translate interface`), in its own PR per locale. - **`review-translation`** checks translated pages against the English, fixes - clear problems, and reports the rest. Run it in a fresh session after - `/translate pages`, before committing. + clear problems, and reports the rest. Commit the translation, then run it in a + fresh session. `update-content` and `translate` open PRs, so they only run when someone asks for them by name (`/update-content`, `/translate`). When another skill hands off From ce3ea96ecfdeb60913e4348ba0f794dd9fdb83cc Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Fri, 2 Oct 2026 09:09:48 +0100 Subject: [PATCH 24/34] Trim the translate skills and drop the HTML review view - side-by-side.js: remove the HTML output and --base. --check now also catches English -ed/-ing forms and stops matching "mejor" for "mejorar". - pages.md: cut repetition; fenced blocks in one place; check list ends with build, commit, /review-translation. - review-translation: report by page URL and section heading, with the English, before, and after in full. - Glossary terms marked translate: true use the word for the locale, and the skill stops if a locale has none. glossary.yml points to STYLE.md. --- .agents/skills/review-translation/SKILL.md | 35 ++-- .agents/skills/translate/SKILL.md | 15 +- .agents/skills/translate/pages.md | 183 ++++++++------------ .agents/skills/translate/side-by-side.js | 188 ++++++++++----------- AGENTS.md | 3 +- glossary.yml | 2 +- 6 files changed, 199 insertions(+), 227 deletions(-) diff --git a/.agents/skills/review-translation/SKILL.md b/.agents/skills/review-translation/SKILL.md index 538efe8e5ea5..38927ca4bc35 100644 --- a/.agents/skills/review-translation/SKILL.md +++ b/.agents/skills/review-translation/SKILL.md @@ -40,20 +40,20 @@ Then read the rules listed above. These need no judgement, so search for them across the whole scope rather than reading for them: -- Run the glossary check on every block. Leave out `--base`: the translation is - already committed, so `--base HEAD` would skip every block. +- Run the check: ```bash node .agents/skills/translate/side-by-side.js docs/.md... --check ``` -- Every word or phrase `.md` says not to use, such as the "Not" column - of a word table. + Each block it lists is a candidate, not a verdict: read it against the + English. It skips rules with their context in brackets, like "upgrade (a + plan)", so search for those by hand. + - Punctuation `.md` rules out, such as curly quotes. - English terms kept from `glossary.yml` written with a capital mid-sentence, if `.md` says to lower-case them. Case-sensitive terms (OpenFn, Canvas, Inspector) and names of things in the app keep their capitals. -- Every `term` and `avoid` rule for the locale in `translation-rules.yml`. ## 3. Read each block against its English @@ -79,33 +79,34 @@ the translation matching it. Fix a block when the problem and the fix are both clear. Change only the words that are wrong, so the diff shows just the fix. Leave the front matter alone. Then run `yarn prettier --write ` on the pages you changed, and run the -glossary check again. +check again. ## 5. Report -Report in the chat, grouped like this, with the file and line for each item: +Build the site and serve it (`yarn serve`), so each item can point to the page +as the reader sees it. Report in the chat. Place each item by the page URL, such +as `http://localhost:3000/es/documentation/build/triggers`, and the section +heading, not by file and line. Group the items like this: -- **Fixed.** Before and after, and why, one line each. +- **Fixed.** For each fix, give the English sentence, the translation before, + and the translation after, all in full, with the changed words in bold. Then + say why in one line. - **Needs a decision.** Problems you were not sure how to fix, or where the fix - changes meaning. + changes meaning. Quote the English and the current translation. - **Problems in the English.** - **Suggested rules.** A problem found on more than one page, or likely to come back in later sections, with the line you would add to `.md` or `translation-rules.yml`. Add it only if asked. -- **Page to spot-check.** The page with the most fixes, and the command to open - it side by side: - - ```bash - node .agents/skills/translate/side-by-side.js docs/.md > review.html - ``` +- **Page to spot-check.** The page with the most fixes, with its URL. ## Marking a page human-reviewed Only a person who reads the language well marks a page `human-reviewed`. It means they checked the meaning against the English, not only that the translation reads well: a page can read perfectly and still say something -different. Using `review.html`, they check every block for the points in step 3, -fix what is wrong, and then set in the front matter: +different. With the translated page open next to the English one on the built +site, they check every paragraph for the points in step 3, fix what is wrong, +and then set in the front matter: ```yaml translation_review_status: human-reviewed diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index 99cbeefb6d80..08d41b7cb0cb 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -36,13 +36,15 @@ or articles and blog posts. ## Before you start -Check these five things. If any fails, stop and ask. +Check these six things. If any fails, stop and ask. - The locale is enabled in `docusaurus.config.js`. Do not enable it yourself; that changes what gets deployed. - `i18n/` is not in `.gitignore`. - `glossary.yml` and `translation-rules.yml` are valid YAML. - The locale has a house style guide, `.md`, in this folder. +- Every `glossary.yml` term marked `translate: true` has a word for the locale + under `locales`. If one is missing, a person decides it. - Your branch has everything on `main`. Run `git fetch origin main` and then `git merge-base --is-ancestor origin/main HEAD`. If it fails, the English you would translate is out of date, and the hashes you record will not match @@ -53,17 +55,18 @@ Check these five things. If any fails, stop and ask. These apply to both tasks. -- Words in `glossary.yml` stay in English. For ordinary words that are also - product terms, like "run" or "step", keep the English only when the word means - the OpenFn thing. +- Terms in `glossary.yml` stay in English, unless they are marked + `translate: true`. Those use the word under `locales.` every time, + such as "proyecto" for project. For ordinary words that are also product + terms, like "run" or "step", keep the English only when the word means the + OpenFn thing. - Follow the house style for the locale in `.md` in this folder, such as `es.md`, and any rules for the locale in `translation-rules.yml`. - Write the way a native writer would, not word for word. Reorder or split a sentence when the literal version is awkward, drop a subject the sentence has already given, and cut an aside that repeats what the sentence says. Keep the meaning and the facts; change only the wording. -- Copy code blocks and inline code exactly. You may translate comments inside - code. +- Copy code blocks and inline code exactly, comments included. - Keep the names of things in the app, like buttons, menus, tabs, and field labels, exactly as they are in the English. The app is English only, so a translated button name points the reader at a button that does not exist. diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 5071a0d18c7b..b59db527b060 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -7,7 +7,7 @@ Save each translation at the same path as the English page, under `i18n//docusaurus-plugin-content-docs/current/`. For example, `docs/build/triggers.md` goes to `i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md`. Docusaurus -ignores a file anywhere else without an error, and the page stays English. +silently ignores a file anywhere else. Translate the English page exactly as it is on disk, so the hash you record matches what you translated. Do not reformat it; English changes belong in their @@ -20,47 +20,38 @@ Copy the English page's front matter. Translate only `title` and `sidebar_label`. Then add: ```yaml -translation_source_hash: +translation_source_hash: .md> translation_review_status: machine ``` -The hash is the content hash of the English file, from -`git hash-object docs/.md`, not a commit. Commits do not survive squash -merges: a hash pointing at a commit made on a branch dangles as soon as the -branch is squashed onto main. A content hash is the same wherever the file -lives, and it answers the only question the field exists to answer: is the -English still the version this was translated from? To compare, hash the current -English file and check it against the recorded value. +The hash is the English file's content hash, not a commit, because commits do +not survive squash merges. `translation_review_status` can be `machine`, `needs-review`, or -`human-reviewed`. Only a human ever sets `human-reviewed`, and when they do they -also add `translation_reviewer` and `translation_review_date`. +`human-reviewed`. Only a human sets `human-reviewed`, and adds +`translation_reviewer` and `translation_review_date` with it. ## Decide what to do with each page -- **No translation yet.** Translate the whole page. -- **The hash matches the current English file.** Skip it, whatever its status. - The English has not changed since it was translated. The one exception: if - `glossary.yml`, `translation-rules.yml`, or the locale's house style - (`.md`) was committed more recently than a `machine` or `needs-review` - page (compare `git log -1 --format=%ct -- `), apply the new rules to it - (see "When the rules change" below). -- **The hash no longer matches, and the status is `machine`, `needs-review`, or - missing.** Translate only what changed (see "Updating a page" below). -- **The hash no longer matches, and the status is `human-reviewed`.** Leave the - file out of the translation PR. Instead, open a separate PR for the named - reviewer that changes only the affected parts. Recover the English the - reviewer saw with `git cat-file -p `, diff it against the - current English, and translate only what changed. In the same PR, set - `translation_source_hash` to the current English hash and leave the status as - `human-reviewed`: the reviewer merging it approves it. If the old version is - no longer in the repo, say so and offer a full retranslation in that PR - instead. +- **No translation yet.** Translate the whole page. To retranslate a page from + scratch on purpose, delete it first. +- **The hash matches the current English.** Skip it, unless `glossary.yml`, + `translation-rules.yml`, or `.md` was committed after a `machine` or + `needs-review` page (compare `git log -1 --format=%ct -- `). Then see + "When the rules change". +- **The hash does not match, and the status is `machine`, `needs-review`, or + missing.** See "Updating a page". +- **The hash does not match, and the status is `human-reviewed`.** Leave it out + of the translation PR. Open a separate PR for the named reviewer: recover the + English they saw with `git cat-file -p `, diff it against the + current English, and translate only what changed. Set the new hash and leave + the status as `human-reviewed`; the reviewer merging it approves it. If the + old English is no longer in the repo, say so and offer a full retranslation. ## When the rules change -Do not retranslate the page. A full retranslation rewords every block, and a -reviewer can no longer see what the new rules changed. +Do not retranslate the page; a reviewer could no longer see what the rules +changed. 1. See what changed in the rules since the page was last committed: @@ -68,79 +59,47 @@ reviewer can no longer see what the new rules changed. git diff $(git log -1 --format=%H -- ) -- glossary.yml translation-rules.yml .agents/skills/translate/.md ``` -2. Fix only the text that breaks a rule that was added or changed. Leave - everything else, even wording you would now write differently. -3. Leave fenced blocks as they are. If one breaks a new rule, say so in the PR. +2. Fix only the text that breaks a new or changed rule. Leave everything else, + even wording you would now write differently. -If nothing breaks the new rules, there is nothing to commit, and the page is -checked again on the next run. That is quick, because only the changed rules are -checked. - -To retranslate a page from scratch on purpose, for example while tuning the -rules on a first section, delete it first. It then counts as having no -translation. +If nothing breaks the new rules, there is nothing to commit. ## Updating a page -Retranslating a whole page rewords text that has not changed, and a reviewer can -no longer see what did. So translate only the blocks whose English changed: +Translate only the blocks whose English changed, so the reviewer sees only what +changed: ```bash node .agents/skills/translate/side-by-side.js docs/.md --json ``` -This prints a JSON array with one entry per page. Its `blocks` are the blocks of -the current English. Each has the existing `translation` to reuse, or `null` -where the English is new or changed. Write the page as those blocks in order, -separated by blank lines: copy each reused translation exactly, and work out -each `null` block. Then update the front matter and run Prettier as usual. +Each page's `blocks` are the blocks of the current English, each with the +`translation` to reuse, or `null` where the English is new or changed. Write the +page as those blocks in order, separated by blank lines: copy each reused +translation exactly, and work out each `null` block. A changed block usually has a `previous` field: the old English, its old -translation, and a word `diff` between the old and new English, marked -`[-removed-]` and `{+added+}`. Use the diff to decide: - -- If only links, inline code, or heading anchors changed, keep the old - translation and copy those changes into it. -- If the prose changed but the old translation already says what the new English - says, keep it as it is. A typo fix in the English often needs nothing. -- Otherwise, translate the block. Reuse the old wording where it still fits, so - the reviewer sees only what changed. - -A block with no `previous` is new, or could not be paired with an old block. -Translate it. - -Update `translation_source_hash` even if no translated text changed. It records -that the translation was checked against this English. - -- A block with `fenced: true` was inside a `` fence. - Put the fence back around it. If `unusedFenced` is not empty, the English a - fenced block corresponds to has changed or gone. Keep the block and ask what - to do with it. -- If the result has an `error`, or `aligned` is `false`, the old translation - cannot be matched to its English. Translate the whole page again, keeping - fenced blocks, and say so in the PR. - -In the PR description, list for each page how many blocks changed and how many -of those needed new translated text. - -## Reviewing +translation, and a word `diff` marked `[-removed-]` and `{+added+}`. -To read a translation next to the English it was translated from: +- If only links, inline code, or heading anchors changed, copy those changes + into the old translation. +- If the old translation already says what the new English says, keep it. A typo + fix often needs nothing. +- Otherwise, translate the block, reusing the old wording where it still fits. -```bash -node .agents/skills/translate/side-by-side.js docs/.md... > review.html -``` +A block with no `previous` is new. Translate it. -Add `--base `, such as `--base origin/i18n`, to highlight the blocks that -changed since the translation at that ref. Put both commands in the PR -description, with the page paths filled in. +If the result has an `error`, or `aligned` is `false`, the old translation +cannot be matched to its English. Translate the whole page again, keeping fenced +blocks, and say so in the PR. -To check a translation against the English and fix it, run `/review-translation` -in a fresh session. +Update `translation_source_hash` even if no translated text changed. In the PR +description, list for each page how many blocks changed and how many needed new +text. ## Fenced blocks -A human can wrap part of a translation like this: +A human can wrap a corrected part of a translation like this: ```markdown @@ -149,9 +108,10 @@ Text a reviewer has corrected by hand. ``` -Copy those blocks into the new translation exactly, in the same place. If the -English they correspond to has been deleted, keep the block anyway and ask what -to do with it. +Copy fenced blocks exactly, in the same place. In the `--json` output they have +`fenced: true`; put the fence back around them. If `unusedFenced` is not empty, +the English a fenced block belongs to has changed or gone: keep the block and +ask what to do with it. If a fenced block breaks a rule, say so in the PR. ## Page rules @@ -159,28 +119,35 @@ These are on top of the translation rules in `SKILL.md`. - Keep the same structure: same headings at the same levels, same lists, same callouts, same components. -- Keep links exactly as they are in the English. Do not add the locale, like - `/es/`; Docusaurus adds it when it builds the page. If the English has a - relative link like `../deploy/portability.md`, it breaks the translated build, - so fix it in the English first (see `STYLE.md`). -- Give translated headings the original English anchor so existing links still - work. +- Keep links exactly as they are in the English. Do not add `/es/`; Docusaurus + adds it. A relative link like `../deploy/portability.md` breaks the translated + build, so fix it in the English first (see `STYLE.md`). +- Give each translated heading its English anchor, as `{#anchor}` after the + heading, so existing links still work. To list the anchors, run this on the + English page: -## Check each page + ```bash + node -e "const s=new (require('github-slugger'))();for(const l of require('fs').readFileSync(process.argv[1],'utf8').split('\n')){const m=l.match(/^#+ (.*?)(?: \{#(.+)\})?$/);if(m)console.log(m[2]||s.slug(m[1]),' ',l)}" docs/.md + ``` -Check that no fixed glossary term was translated: +## Check each page ```bash -node .agents/skills/translate/side-by-side.js docs/.md... --check --base HEAD +node .agents/skills/translate/side-by-side.js docs/.md... --check ``` -This lists each block where a fixed term (OpenFn, Lightning, adaptor, work -order, and so on) appears fewer times in the translation than in the English. -With `--base HEAD`, it checks only blocks whose English changed since the last -commit, so a block that was already looked at is not raised again. Look at each -one. If the term was translated, put the English term back. If the sentence just -uses it fewer times, for example because Spanish drops a repeated subject, leave -it: do not add the term back to match the count. Check the code blocks are -identical. Check the counts of headings, code blocks, callouts, images, and -tables match. Check the front matter is complete. Check every fenced block -survived. Then build and open the PR as `SKILL.md` describes. +This lists blocks where a fixed glossary term appears fewer times in the +translation than in the English, or that may break a rule in +`translation-rules.yml` or `.md`. Each is a candidate. If a term was +translated, put the English back. If Spanish just uses it fewer times, for +example by dropping a repeated subject, leave it. + +Then check by hand that: + +- code blocks are identical to the English +- the counts of headings, code blocks, callouts, images, and tables match +- the front matter is complete +- every fenced block survived + +Then build as `SKILL.md` describes, commit, and run `/review-translation` in a +fresh session. diff --git a/.agents/skills/translate/side-by-side.js b/.agents/skills/translate/side-by-side.js index c069efe78ea4..625499df0738 100644 --- a/.agents/skills/translate/side-by-side.js +++ b/.agents/skills/translate/side-by-side.js @@ -1,15 +1,8 @@ #!/usr/bin/env node // Lines up an English page with its translation, block by block. // -// node .agents/skills/translate/side-by-side.js es docs/get-started/*.md > review.html -// node .agents/skills/translate/side-by-side.js es docs/build/triggers.md --base origin/i18n > review.html // node .agents/skills/translate/side-by-side.js es docs/build/triggers.md --json -// node .agents/skills/translate/side-by-side.js es docs/get-started/*.md --check --base HEAD -// -// The HTML pairs each block of the English the page was translated from (its -// translation_source_hash) with the translated block. --base highlights -// the blocks whose English changed since the translation at , to review -// an update. +// node .agents/skills/translate/side-by-side.js es docs/get-started/*.md --check // // --json lists the blocks of the current English, each with the translation // that can be reused for it, or null where the English is new or changed. A @@ -17,9 +10,9 @@ // word diff, under previous. See "Updating a page" in pages.md. // // --check lists blocks where a fixed glossary term appears fewer times in the -// translation than in the English, which can mean it was translated. With -// --base, only blocks whose English changed since are checked, so a -// difference someone has already looked at is not raised again. +// translation than in the English, which can mean it was translated. It also +// lists blocks that may break a rule for the locale in translation-rules.yml, +// or use a word from the "Not" column in .md. // // A block is a run of lines between blank lines, after formatting with // Prettier, and a fenced code block is one block. Both sides go through @@ -54,7 +47,70 @@ const termDrops = (english, translation) => term, en: (prose(english).match(re) || []).length, es: (prose(translation).match(re) || []).length, - })).filter(c => c.es < c.en); + })) + .filter(c => c.es < c.en) + .map(c => `"${c.term}" ${c.en} in English, ${c.es} in translation`); + +// Phrase matching for the locale's rules. Accents and case are ignored, and +// inline code is skipped. "a / b" matches either. A word may take a plural, so +// "feature" finds "features" and "función" finds "funciones" but not +// "funcionalidad". An English phrase also matches its -d, -ed, and -ing forms, +// so "enable" finds "enabled" and "enabling". A one-word Spanish verb matches +// its conjugations, so "mejorar" finds "mejora" and "mejorado" but not "mejor". +const fold = s => + s.normalize('NFD').replace(/\p{M}/gu, '').toLowerCase().replace(/\s+/g, ' '); +const has = (text, phrase, english = false) => { + const t = fold(prose(text).replace(/`[^`]*`/g, '')); + return phrase.split('/').some(alt => { + const p = fold(alt.replace(/\s*\(.*?\)/g, '').trim()); + const verb = !english && !p.includes(' ') && /(ar|er|ir)$/.test(p); + const stem = english + ? p.replace(/e$/, '') + : verb + ? p.replace(/(ar|er|ir)$/, '') + : p; + const end = english + ? '(?:e|es|s|ed|d|ing)?(?!\\p{L})' + : verb + ? '[aeio]\\p{L}*' + : '(?:e?s)?(?!\\p{L})'; + const escaped = stem.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(`(?.md. Each returns the problems in one block. A rule +// whose source names its context, like "upgrade (a plan)", is left out: only a +// reader can tell which uses it covers. +function rulesFor(locale) { + const rules = ( + yaml.load(fs.readFileSync('translation-rules.yml', 'utf8')).rules || [] + ).filter( + r => (r.locale === locale || r.locale === '*') && !r.source.includes('(') + ); + const styleFile = `.agents/skills/translate/${locale}.md`; + const banned = fs.existsSync(styleFile) + ? fs + .readFileSync(styleFile, 'utf8') + .split('\n') + .map(l => l.match(/^\|([^|]+)\|([^|]+)\|$/)) + .filter(m => m && !/^[\s-]+$/.test(m[2]) && m[2].trim() !== 'Not') + .map(m => ({ use: m[1].trim(), not: m[2].trim() })) + : []; + return (english, translation) => [ + ...rules + .filter(r => + r.kind === 'avoid' + ? has(english, r.source, true) && has(translation, r.target) + : has(english, r.source, true) && !has(translation, r.target) + ) + .map(r => `rule "${r.source}": ${r.instruction.replace(/\s+/g, ' ')}`), + ...banned + .filter(b => has(translation, b.not)) + .map(b => `${locale}.md: use "${b.use}", not "${b.not}"`), + ]; +} // Word diff in git's --word-diff=plain style: [-removed-]{+added+}. function wordDiff(a, b) { @@ -153,7 +209,7 @@ async function sourceOf(esText, enPath) { } } -async function page(locale, enPath, base) { +async function page(locale, enPath) { const rel = enPath.replace(/^docs\//, ''); const esPath = `i18n/${locale}/docusaurus-plugin-content-docs/current/${rel}`; const result = { page: rel, notes: [] }; @@ -226,117 +282,61 @@ async function page(locale, enPath, base) { .filter((b, i) => b.fenced && !used.has(i)) .map(b => b.text); - // For the HTML view: the source English next to the translation. - result.rows = source.blocks.map((b, i) => ({ - english: b.text, - translation: esBlocks[i]?.text ?? '', - })); - for (const b of esBlocks.slice(source.blocks.length)) - result.rows.push({ english: '', translation: b.text }); - - if (base) { - let before; - try { - before = git('show', `${base}:${esPath}`); - } catch { - result.notes.push(`New translation since ${base}.`); - } - if (before) { - const old = await sourceOf(before, enPath); - const oldTexts = new Set((old.blocks || []).map(b => b.text)); - for (const row of result.rows) - row.changed = row.english !== '' && !oldTexts.has(row.english); - } - } - - // Compare block by block where the blocks line up, otherwise the whole page. + // Compare the source English with the translation block by block where the + // blocks line up, otherwise the whole page. const toCheck = result.aligned - ? result.rows.filter(r => r.changed !== false) + ? source.blocks.map((b, i) => ({ + english: b.text, + translation: esBlocks[i].text, + })) : [ { english: source.blocks.map(b => b.text).join('\n\n'), translation: esBlocks.map(b => b.text).join('\n\n'), }, ]; - result.termDrops = toCheck - .map(r => ({ ...r, drops: termDrops(r.english, r.translation) })) - .filter(r => r.drops.length); + const ruleBreaks = rulesFor(locale); + result.problems = toCheck + .map(r => ({ + ...r, + found: [ + ...termDrops(r.english, r.translation), + ...ruleBreaks(r.english, r.translation), + ], + })) + .filter(r => r.found.length); return result; } -const esc = s => - s.replace(/&/g, '&').replace(//g, '>'); - -function html(pages) { - const sections = pages.map(p => { - const notes = [p.error, ...(p.notes || [])] - .filter(Boolean) - .map(n => `

${esc(n)}

`) - .join(''); - const rows = (p.rows || []) - .map( - r => - `
${esc(r.english)}
${esc(r.translation)}
` - ) - .join('\n'); - return `

${esc(p.page)}

${notes}${rows}`; - }); - return ` -Translation review - -

Translation review

-

Each box is one block of the English the page was translated from (grey, on top), with its translation underneath. Boxes with a yellow border changed since the base.

-${sections.join('\n')} - -`; -} - async function main() { const args = process.argv.slice(2); const json = args.includes('--json'); const check = args.includes('--check'); - const baseAt = args.indexOf('--base'); - const base = baseAt === -1 ? null : args[baseAt + 1]; - const [locale, ...files] = args.filter( - (a, i) => !a.startsWith('--') && (baseAt === -1 || i !== baseAt + 1) - ); - if (!locale || !files.length) { + const [locale, ...files] = args.filter(a => !a.startsWith('--')); + if (!locale || !files.length || json === check) { console.error( - 'Usage: side-by-side.js ... [--base ] [--json | --check]' + 'Usage: side-by-side.js ... --json | --check' ); process.exit(1); } const pages = []; - for (const f of files) pages.push(await page(locale, f, base)); + for (const f of files) pages.push(await page(locale, f)); if (check) { const flat = s => s.replace(/\s+/g, ' '); let found = 0; for (const p of pages) { if (p.error) console.log(`${p.page}: ${p.error}`); - for (const r of p.termDrops || []) { + for (const r of p.problems || []) { found++; - const counts = r.drops.map( - d => `"${d.term}" ${d.en} in English, ${d.es} in translation` - ); console.log( - `${p.page}: ${counts.join('; ')}\n en: ${flat(r.english)}\n ${locale}: ${flat(r.translation)}\n` + `${p.page}: ${r.found.join('; ')}\n en: ${flat(r.english)}\n ${locale}: ${flat(r.translation)}\n` ); } } - if (!found) console.log('No glossary terms missing.'); - } else if (json) { - const out = pages.map(({ rows, termDrops, ...p }) => p); - console.log(JSON.stringify(out, null, 2)); + if (!found) console.log('No glossary terms missing and no rules broken.'); } else { - process.stdout.write(html(pages)); + const out = pages.map(({ problems, ...p }) => p); + console.log(JSON.stringify(out, null, 2)); } } diff --git a/AGENTS.md b/AGENTS.md index b24affced83e..2612add27f95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,7 +74,8 @@ category from the sidebar, one folder under `docs/`, or one page. - Never edit generated adaptor pages. Draft an issue for `OpenFn/adaptors` and put it in the PR. Only file it if asked. - Never retranslate text inside `` fences. -- Never translate a term listed in `glossary.yml`. +- Never translate a term listed in `glossary.yml`, unless it is marked + `translate: true`. Then use the word given for the locale. - Never retake, crop, or replace screenshots. - Never disable a check to make the build pass. diff --git a/glossary.yml b/glossary.yml index 59ccd05afb41..01401fe6bf03 100644 --- a/glossary.yml +++ b/glossary.yml @@ -6,7 +6,7 @@ # # 1. The translate skill (.agents/skills/translate/SKILL.md). Any term with # `translate: false` must appear verbatim in every translated page. -# 2. The house style in AGENTS.md. Any spelling in `variants` is replaced +# 2. The house style in STYLE.md. Any spelling in `variants` is replaced # with `term` in English pages. # # Humans maintain this file. Add a term when a review shows the same From 7d43153810fed12aa3795c446bfba1983de42cc9 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Fri, 2 Oct 2026 09:16:59 +0100 Subject: [PATCH 25/34] Keep glossary terms in English unless locales gives a word Every glossary term is translate: false again. A term's locales entry (es: proyecto) overrides the English for that locale only, so a new locale keeps the English until someone adds a word for it. --- .agents/skills/translate/SKILL.md | 13 +++++-------- AGENTS.md | 4 ++-- glossary.yml | 21 +++++++++++++-------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index 08d41b7cb0cb..91fef3959e63 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -36,15 +36,13 @@ or articles and blog posts. ## Before you start -Check these six things. If any fails, stop and ask. +Check these five things. If any fails, stop and ask. - The locale is enabled in `docusaurus.config.js`. Do not enable it yourself; that changes what gets deployed. - `i18n/` is not in `.gitignore`. - `glossary.yml` and `translation-rules.yml` are valid YAML. - The locale has a house style guide, `.md`, in this folder. -- Every `glossary.yml` term marked `translate: true` has a word for the locale - under `locales`. If one is missing, a person decides it. - Your branch has everything on `main`. Run `git fetch origin main` and then `git merge-base --is-ancestor origin/main HEAD`. If it fails, the English you would translate is out of date, and the hashes you record will not match @@ -55,11 +53,10 @@ Check these six things. If any fails, stop and ask. These apply to both tasks. -- Terms in `glossary.yml` stay in English, unless they are marked - `translate: true`. Those use the word under `locales.` every time, - such as "proyecto" for project. For ordinary words that are also product - terms, like "run" or "step", keep the English only when the word means the - OpenFn thing. +- Terms in `glossary.yml` stay in English, unless the term lists a word for the + locale under `locales`, such as `es: proyecto`. For ordinary words that are + also product terms, like "run" or "step", keep the English only when the word + means the OpenFn thing. - Follow the house style for the locale in `.md` in this folder, such as `es.md`, and any rules for the locale in `translation-rules.yml`. - Write the way a native writer would, not word for word. Reorder or split a diff --git a/AGENTS.md b/AGENTS.md index 2612add27f95..339157d2ff43 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,8 +74,8 @@ category from the sidebar, one folder under `docs/`, or one page. - Never edit generated adaptor pages. Draft an issue for `OpenFn/adaptors` and put it in the PR. Only file it if asked. - Never retranslate text inside `` fences. -- Never translate a term listed in `glossary.yml`, unless it is marked - `translate: true`. Then use the word given for the locale. +- Never translate a term listed in `glossary.yml`, unless the term lists a word + for the locale under `locales`. - Never retake, crop, or replace screenshots. - Never disable a check to make the build pass. diff --git a/glossary.yml b/glossary.yml index 01401fe6bf03..90ff040a0be2 100644 --- a/glossary.yml +++ b/glossary.yml @@ -5,7 +5,8 @@ # Product vocabulary for the OpenFn docs. Two consumers read this file: # # 1. The translate skill (.agents/skills/translate/SKILL.md). Any term with -# `translate: false` must appear verbatim in every translated page. +# `translate: false` must appear verbatim in every translated page, +# unless `locales` gives a word for that page's locale. # 2. The house style in STYLE.md. Any spelling in `variants` is replaced # with `term` in English pages. # @@ -25,9 +26,9 @@ # case_sensitive: boolean # true = the house-style check flags case variants too (default false) # variants: [string] # spellings the house-style check flags, to replace with `term` # note: string # guidance for humans and the agent -# locales: # optional. Only used when translate: true, to pin a -# es: string # specific rendering per locale instead of free -# fr: string # translation. +# locales: # optional. The word a locale uses instead of the +# es: string # English. Overrides translate: false for that +# fr: string # locale only; other locales keep the English. # # patterns: # regexes that are never translated, for families # - pattern: string # of identifiers too numerous to list (adaptor @@ -85,7 +86,8 @@ terms: migration. - term: credential - translate: true + translate: false + product_noun: true note: >- Stored authentication configuration attached to a Step. Spanish translates it because "credencial" looks almost the same as the English in the app. @@ -133,7 +135,8 @@ terms: note: Billing and hosting unit on the hosted OpenFn app. - term: project - translate: true + translate: false + product_noun: true note: >- Administrative grouping of workflows, credentials, and collaborators. Spanish translates it because "proyecto" looks almost the same as the @@ -149,7 +152,8 @@ terms: note: A stored input or output state object. - term: collection - translate: true + translate: false + product_noun: true note: >- The Collections key-value store feature. Spanish translates it because "colección" looks almost the same as the English in the app. Decide @@ -185,7 +189,8 @@ terms: names the object (usually rendered in code as `state`). - term: operation - translate: true + translate: false + product_noun: true note: >- A function exported by an adaptor, e.g. `get()`, `upsert()`. Spanish translates it because "operación" looks almost the same as the English in From ca715a4002f26a500ceb96ecfd804081e7b873bd Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Fri, 2 Oct 2026 11:18:53 +0100 Subject: [PATCH 26/34] Add the language dropdown to the navbar --- docusaurus.config.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index dc9832a74aac..dfec6813de25 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -13,7 +13,7 @@ module.exports = { organizationName: 'openfn', projectName: 'docs', // --- i18n (internationalization) --- - // Spanish is built at /es/ but not linked from the navbar until it launches. + // Spanish is built at /es/ and linked from the navbar's language dropdown. // Translated content lives in i18n//. Anything not translated // falls back to the English source automatically. i18n: { @@ -95,6 +95,10 @@ module.exports = { type: 'docsVersionDropdown', position: 'right', }, + { + type: 'localeDropdown', + position: 'right', + }, { href: 'https://github.com/openfn/docs', position: 'right', From ef4182b75e1b8066709ab7a282d717b59e24f2c7 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Mon, 5 Oct 2026 15:03:44 +0100 Subject: [PATCH 27/34] Check for rule changes by commit ancestry, not commit time --- .agents/skills/translate/pages.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index b59db527b060..c1a79cd87590 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -35,10 +35,14 @@ not survive squash merges. - **No translation yet.** Translate the whole page. To retranslate a page from scratch on purpose, delete it first. -- **The hash matches the current English.** Skip it, unless `glossary.yml`, - `translation-rules.yml`, or `.md` was committed after a `machine` or - `needs-review` page (compare `git log -1 --format=%ct -- `). Then see - "When the rules change". +- **The hash matches the current English.** Skip it, unless the page is + `machine` or `needs-review` and this lists any commits: + + ```bash + git log --oneline $(git log -1 --format=%H -- )..HEAD -- glossary.yml translation-rules.yml .agents/skills/translate/.md + ``` + + Then see "When the rules change". - **The hash does not match, and the status is `machine`, `needs-review`, or missing.** See "Updating a page". - **The hash does not match, and the status is `human-reviewed`.** Leave it out From fdffd1b2fdc7ef10c1e38d6898fd979772c57e64 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Mon, 5 Oct 2026 15:31:36 +0100 Subject: [PATCH 28/34] Update translations from a git diff of the English, and fix the check script Drop side-by-side.js --json: it lost translations for repeated blocks and missed moved ones. Updates now edit the translation in place from a word diff of the English. --check skips rules missing source or target, and no longer flags terms the locale translates under locales. Heading anchors now come from docusaurus write-heading-ids, which skips code blocks and strips links. --- .agents/skills/review-translation/SKILL.md | 7 +- .agents/skills/translate/pages.md | 59 +++-- .agents/skills/translate/side-by-side.js | 275 ++++++--------------- 3 files changed, 109 insertions(+), 232 deletions(-) diff --git a/.agents/skills/review-translation/SKILL.md b/.agents/skills/review-translation/SKILL.md index 38927ca4bc35..d1eaee2cd89a 100644 --- a/.agents/skills/review-translation/SKILL.md +++ b/.agents/skills/review-translation/SKILL.md @@ -27,11 +27,8 @@ reports the rest. It does not commit, open a PR, or add rules. Never edit a ## 1. Lay out the pages -Get each block of English next to its translation: - -```bash -node .agents/skills/translate/side-by-side.js docs/.md... --json -``` +Read each translated page next to its English page in `docs/`. The translation +keeps the same structure, so the blocks line up in order. Then read the rules listed above. diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index c1a79cd87590..535005b7d443 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -43,14 +43,15 @@ not survive squash merges. ``` Then see "When the rules change". + - **The hash does not match, and the status is `machine`, `needs-review`, or missing.** See "Updating a page". - **The hash does not match, and the status is `human-reviewed`.** Leave it out - of the translation PR. Open a separate PR for the named reviewer: recover the - English they saw with `git cat-file -p `, diff it against the - current English, and translate only what changed. Set the new hash and leave - the status as `human-reviewed`; the reviewer merging it approves it. If the - old English is no longer in the repo, say so and offer a full retranslation. + of the translation PR. Open a separate PR for the named reviewer: diff the + English they saw against the current English as in "Updating a page", and + translate only what changed. Set the new hash and leave the status as + `human-reviewed`; the reviewer merging it approves it. If the old English is + no longer in the repo, say so and offer a full retranslation. ## When the rules change @@ -70,32 +71,24 @@ If nothing breaks the new rules, there is nothing to commit. ## Updating a page -Translate only the blocks whose English changed, so the reviewer sees only what -changed: +Change only what the English changed, so the reviewer sees only that. Diff the +English the page was translated from against the current English: ```bash -node .agents/skills/translate/side-by-side.js docs/.md --json +git diff --word-diff HEAD:docs/.md ``` -Each page's `blocks` are the blocks of the current English, each with the -`translation` to reuse, or `null` where the English is new or changed. Write the -page as those blocks in order, separated by blank lines: copy each reused -translation exactly, and work out each `null` block. - -A changed block usually has a `previous` field: the old English, its old -translation, and a word `diff` marked `[-removed-]` and `{+added+}`. +Edit the existing translation in place, block by block: - If only links, inline code, or heading anchors changed, copy those changes - into the old translation. -- If the old translation already says what the new English says, keep it. A typo - fix often needs nothing. -- Otherwise, translate the block, reusing the old wording where it still fits. + into the translation. +- If the translation already says what the new English says, keep it. A typo fix + often needs nothing. +- Otherwise, translate the changed text, reusing the old wording where it still + fits. -A block with no `previous` is new. Translate it. - -If the result has an `error`, or `aligned` is `false`, the old translation -cannot be matched to its English. Translate the whole page again, keeping fenced -blocks, and say so in the PR. +If the recorded English is not in the repo (`git diff` fails), translate the +whole page again, keeping fenced blocks, and say so in the PR. Update `translation_source_hash` even if no translated text changed. In the PR description, list for each page how many blocks changed and how many needed new @@ -112,10 +105,9 @@ Text a reviewer has corrected by hand. ``` -Copy fenced blocks exactly, in the same place. In the `--json` output they have -`fenced: true`; put the fence back around them. If `unusedFenced` is not empty, -the English a fenced block belongs to has changed or gone: keep the block and -ask what to do with it. If a fenced block breaks a rule, say so in the PR. +Leave fenced blocks exactly as they are, in the same place. If the English a +fenced block belongs to has changed or gone, keep the block and ask what to do +with it. If a fenced block breaks a rule, say so in the PR. ## Page rules @@ -127,13 +119,18 @@ These are on top of the translation rules in `SKILL.md`. adds it. A relative link like `../deploy/portability.md` breaks the translated build, so fix it in the English first (see `STYLE.md`). - Give each translated heading its English anchor, as `{#anchor}` after the - heading, so existing links still work. To list the anchors, run this on the - English page: + heading, so existing links still work. To get the anchors, let Docusaurus + write them into a copy of the English page: ```bash - node -e "const s=new (require('github-slugger'))();for(const l of require('fs').readFileSync(process.argv[1],'utf8').split('\n')){const m=l.match(/^#+ (.*?)(?: \{#(.+)\})?$/);if(m)console.log(m[2]||s.slug(m[1]),' ',l)}" docs/.md + cp docs/.md /page.md + yarn docusaurus write-heading-ids . /page.md ``` + It keeps the underscores from `_italic_` text in the anchor, which the site + does not: `See _Now_` gives `see-_now_`, but the site uses `see-now`. Drop + them. + ## Check each page ```bash diff --git a/.agents/skills/translate/side-by-side.js b/.agents/skills/translate/side-by-side.js index 625499df0738..c3397bb2322f 100644 --- a/.agents/skills/translate/side-by-side.js +++ b/.agents/skills/translate/side-by-side.js @@ -1,15 +1,9 @@ #!/usr/bin/env node -// Lines up an English page with its translation, block by block. +// Checks translated pages against the English they were translated from. // -// node .agents/skills/translate/side-by-side.js es docs/build/triggers.md --json // node .agents/skills/translate/side-by-side.js es docs/get-started/*.md --check // -// --json lists the blocks of the current English, each with the translation -// that can be reused for it, or null where the English is new or changed. A -// changed block also gets the old English and translation it replaced, with a -// word diff, under previous. See "Updating a page" in pages.md. -// -// --check lists blocks where a fixed glossary term appears fewer times in the +// Lists blocks where a fixed glossary term appears fewer times in the // translation than in the English, which can mean it was translated. It also // lists blocks that may break a rule for the locale in translation-rules.yml, // or use a word from the "Not" column in .md. @@ -22,16 +16,20 @@ const { execFileSync } = require('child_process'); const prettier = require('prettier'); const yaml = require('js-yaml'); // installed with Docusaurus -const FENCE_OPEN = ''; -const FENCE_CLOSE = ''; +const FENCE_MARKERS = [ + '', + '', +]; -// Terms that must stay in English. Product nouns like "run" are left out, -// since their ordinary-English uses get translated. +// Terms that must stay in English, unless `locales` gives the locale a word. +// Product nouns like "run" are left out, since their ordinary-English uses get +// translated. const FIXED = yaml .load(fs.readFileSync('glossary.yml', 'utf8')) .terms.filter(t => !t.translate && !t.product_noun) .map(t => ({ term: t.term, + locales: t.locales || {}, re: new RegExp( `\\b${t.term.replace(/ /g, '\\s+')}s?\\b`, t.case_sensitive ? 'g' : 'gi' @@ -42,12 +40,13 @@ const FIXED = yaml const prose = s => s.replace(/\{#[^}]*\}/g, '').replace(/\]\([^)]*\)/g, ']'); // A higher count in the translation is harmless; a lower one is worth a look. -const termDrops = (english, translation) => - FIXED.map(({ term, re }) => ({ - term, - en: (prose(english).match(re) || []).length, - es: (prose(translation).match(re) || []).length, - })) +const termDrops = (locale, english, translation) => + FIXED.filter(t => !t.locales[locale]) + .map(({ term, re }) => ({ + term, + en: (prose(english).match(re) || []).length, + es: (prose(translation).match(re) || []).length, + })) .filter(c => c.es < c.en) .map(c => `"${c.term}" ${c.en} in English, ${c.es} in translation`); @@ -82,12 +81,16 @@ const has = (text, phrase, english = false) => { // Rules for one locale: translation-rules.yml, and the "Not" column of the // word table in .md. Each returns the problems in one block. A rule // whose source names its context, like "upgrade (a plan)", is left out: only a -// reader can tell which uses it covers. +// reader can tell which uses it covers. So is a rule missing source or target. function rulesFor(locale) { const rules = ( yaml.load(fs.readFileSync('translation-rules.yml', 'utf8')).rules || [] ).filter( - r => (r.locale === locale || r.locale === '*') && !r.source.includes('(') + r => + (r.locale === locale || r.locale === '*') && + r.source && + r.target && + !r.source.includes('(') ); const styleFile = `.agents/skills/translate/${locale}.md`; const banned = fs.existsSync(styleFile) @@ -112,44 +115,6 @@ function rulesFor(locale) { ]; } -// Word diff in git's --word-diff=plain style: [-removed-]{+added+}. -function wordDiff(a, b) { - const x = a.split(/\s+/); - const y = b.split(/\s+/); - const lcs = x.map(() => Array(y.length + 1).fill(0)); - lcs.push(Array(y.length + 1).fill(0)); - for (let i = x.length - 1; i >= 0; i--) - for (let j = y.length - 1; j >= 0; j--) - lcs[i][j] = - x[i] === y[j] - ? lcs[i + 1][j + 1] + 1 - : Math.max(lcs[i + 1][j], lcs[i][j + 1]); - const out = []; - let i = 0; - let j = 0; - while (i < x.length || j < y.length) { - if (i < x.length && j < y.length && x[i] === y[j]) { - out.push(x[i++]); - j++; - } else if ( - i < x.length && - (j === y.length || lcs[i + 1][j] >= lcs[i][j + 1]) - ) - out.push(`[-${x[i++]}-]`); - else out.push(`{+${y[j++]}+}`); - } - return out - .join(' ') - .replace(/-\] \[-/g, ' ') - .replace(/\+\} \{\+/g, ' '); -} - -const git = (...args) => - execFileSync('git', args, { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'ignore'], - }); - function splitFrontMatter(text) { const m = text.match(/^---\n([\s\S]*?)\n---\n/); return m @@ -157,10 +122,7 @@ function splitFrontMatter(text) { : { fm: '', body: text }; } -const field = (fm, key) => - (fm.match(new RegExp(`^${key}: *(.*)$`, 'm')) || [])[1]; - -// Blocks inside do-not-retranslate fences are flagged, and the markers dropped. +// do-not-retranslate markers are dropped so the blocks line up. async function blocks(text, filepath) { const options = { ...(await prettier.resolveConfig(filepath)), @@ -170,20 +132,13 @@ async function blocks(text, filepath) { const out = []; let cur = []; let inCode = false; - let fenced = false; const flush = () => { - if (cur.length) out.push({ text: cur.join('\n'), fenced }); + if (cur.length) out.push(cur.join('\n')); cur = []; }; for (const line of formatted.split('\n')) { - if (!inCode && line.trim() === FENCE_OPEN) { - flush(); - fenced = true; - continue; - } - if (!inCode && line.trim() === FENCE_CLOSE) { + if (!inCode && FENCE_MARKERS.includes(line.trim())) { flush(); - fenced = false; continue; } if (/^\s*(```|~~~)/.test(line)) inCode = !inCode; @@ -194,150 +149,78 @@ async function blocks(text, filepath) { return out; } -// The English the translation at esPath (or esText) was made from, as blocks. -async function sourceOf(esText, enPath) { - const hash = field(splitFrontMatter(esText).fm, 'translation_source_hash'); - if (!hash) - return { hash, error: 'No translation_source_hash in the front matter.' }; - try { - return { hash, blocks: await blocks(git('cat-file', '-p', hash), enPath) }; - } catch { - return { - hash, - error: `The English it was translated from (${hash}) is not in the repo.`, - }; - } -} - async function page(locale, enPath) { const rel = enPath.replace(/^docs\//, ''); const esPath = `i18n/${locale}/docusaurus-plugin-content-docs/current/${rel}`; - const result = { page: rel, notes: [] }; - if (!fs.existsSync(esPath)) - return { ...result, error: 'No translation yet.' }; + if (!fs.existsSync(esPath)) return { page: rel, error: 'No translation yet.' }; + // Check against the English the page was translated from. const esText = fs.readFileSync(esPath, 'utf8'); - const source = await sourceOf(esText, enPath); - if (source.error) return { ...result, error: source.error }; - const esBlocks = await blocks(esText, esPath); - const current = await blocks(fs.readFileSync(enPath, 'utf8'), enPath); - - result.aligned = source.blocks.length === esBlocks.length; - if (!result.aligned) - result.notes.push( - `The blocks don't line up: ${source.blocks.length} in the English, ${esBlocks.length} in the translation.` - ); - if (source.hash !== git('hash-object', enPath).trim()) - result.notes.push('The English has changed since this was translated.'); - - // Translation to reuse for each English block, keyed by the block's text. - const reuse = new Map(); - if (result.aligned) - source.blocks.forEach((b, i) => reuse.set(b.text, { ...esBlocks[i], i })); - const used = new Set(); - result.blocks = current.map(b => { - const t = reuse.get(b.text); - if (t) used.add(t.i); + const hash = (splitFrontMatter(esText).fm.match( + /^translation_source_hash: *(.*)$/m + ) || [])[1]; + if (!hash) + return { page: rel, error: 'No translation_source_hash in the front matter.' }; + let enBlocks; + try { + const english = execFileSync('git', ['cat-file', '-p', hash], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }); + enBlocks = await blocks(english, enPath); + } catch { return { - english: b.text, - translation: t ? t.text : null, - fenced: t ? t.fenced : false, + page: rel, + error: `The English it was translated from (${hash}) is not in the repo.`, }; - }); - // Pair each changed block with the old block it replaced: a run of changed - // blocks between two reused ones takes the old blocks in the same gap, when - // the counts match. Otherwise the block is new, or the pairing is unclear. - if (result.aligned) { - // Repeated blocks like ":::" match in order, so the next unused one. - let last = -1; - const at = current.map(b => { - const i = source.blocks.findIndex( - (s, k) => k > last && s.text === b.text - ); - if (i !== -1) last = i; - return i === -1 ? undefined : i; - }); - for (let i = 0; i < current.length;) { - if (at[i] !== undefined) { - i++; - continue; - } - let j = i; - while (j < current.length && at[j] === undefined) j++; - const from = i === 0 ? 0 : at[i - 1] + 1; - const to = j === current.length ? source.blocks.length : at[j]; - if (to - from === j - i) - for (let k = 0; k < j - i; k++) { - const old = source.blocks[from + k].text; - result.blocks[i + k].previous = { - english: old, - translation: esBlocks[from + k].text, - diff: wordDiff(old, current[i + k].text), - }; - } - i = j; - } } - result.unusedFenced = esBlocks - .filter((b, i) => b.fenced && !used.has(i)) - .map(b => b.text); + const esBlocks = await blocks(esText, esPath); - // Compare the source English with the translation block by block where the - // blocks line up, otherwise the whole page. - const toCheck = result.aligned - ? source.blocks.map((b, i) => ({ - english: b.text, - translation: esBlocks[i].text, - })) - : [ - { - english: source.blocks.map(b => b.text).join('\n\n'), - translation: esBlocks.map(b => b.text).join('\n\n'), - }, - ]; + // Compare block by block where the blocks line up, otherwise the whole page. + const pairs = + enBlocks.length === esBlocks.length + ? enBlocks.map((english, i) => ({ english, translation: esBlocks[i] })) + : [ + { + english: enBlocks.join('\n\n'), + translation: esBlocks.join('\n\n'), + }, + ]; const ruleBreaks = rulesFor(locale); - result.problems = toCheck - .map(r => ({ - ...r, - found: [ - ...termDrops(r.english, r.translation), - ...ruleBreaks(r.english, r.translation), - ], - })) - .filter(r => r.found.length); - return result; + return { + page: rel, + problems: pairs + .map(r => ({ + ...r, + found: [ + ...termDrops(locale, r.english, r.translation), + ...ruleBreaks(r.english, r.translation), + ], + })) + .filter(r => r.found.length), + }; } async function main() { const args = process.argv.slice(2); - const json = args.includes('--json'); - const check = args.includes('--check'); const [locale, ...files] = args.filter(a => !a.startsWith('--')); - if (!locale || !files.length || json === check) { - console.error( - 'Usage: side-by-side.js ... --json | --check' - ); + if (!locale || !files.length || !args.includes('--check')) { + console.error('Usage: side-by-side.js ... --check'); process.exit(1); } - const pages = []; - for (const f of files) pages.push(await page(locale, f)); - if (check) { - const flat = s => s.replace(/\s+/g, ' '); - let found = 0; - for (const p of pages) { - if (p.error) console.log(`${p.page}: ${p.error}`); - for (const r of p.problems || []) { - found++; - console.log( - `${p.page}: ${r.found.join('; ')}\n en: ${flat(r.english)}\n ${locale}: ${flat(r.translation)}\n` - ); - } + const flat = s => s.replace(/\s+/g, ' '); + let found = 0; + for (const f of files) { + const p = await page(locale, f); + if (p.error) console.log(`${p.page}: ${p.error}`); + for (const r of p.problems || []) { + found++; + console.log( + `${p.page}: ${r.found.join('; ')}\n en: ${flat(r.english)}\n ${locale}: ${flat(r.translation)}\n` + ); } - if (!found) console.log('No glossary terms missing and no rules broken.'); - } else { - const out = pages.map(({ problems, ...p }) => p); - console.log(JSON.stringify(out, null, 2)); } + if (!found) console.log('No glossary terms missing and no rules broken.'); } main(); From aa8b7fbdd5dfc2db6908d3f5fb72f5b1944d3562 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Mon, 5 Oct 2026 15:31:36 +0100 Subject: [PATCH 29/34] Remove the unused tagline from the site config The homepage subtitle now comes from a translatable string, so nothing reads it. --- docusaurus.config.js | 2 -- src/pages/index.js | 3 --- 2 files changed, 5 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index dfec6813de25..fbe5fad0fce5 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -2,8 +2,6 @@ const path = require('path'); module.exports = { title: 'OpenFn/docs', - tagline: - 'The leading digital public good for workflow automation, OpenFn makes ICT4D more efficient.', url: 'https://docs.openfn.org', baseUrl: '/', trailingSlash: false, diff --git a/src/pages/index.js b/src/pages/index.js index 85c4d6962156..c0c5bfc2f75c 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -276,9 +276,6 @@ function Home() {

OpenFn Documentation

- {/* The English copy here mirrors `tagline` in docusaurus.config.js. - Site-level config values are not extracted for translation, so the - hero subtitle is declared as a translatable string instead. */}

The leading digital public good for workflow automation, OpenFn From f5ac4776809dcf73ba83218ad1d54cae55cc2f79 Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Mon, 5 Oct 2026 15:33:58 +0100 Subject: [PATCH 30/34] Rename side-by-side.js to check-translation.js It only runs the check now, so the --check flag goes too. --- .agents/skills/review-translation/SKILL.md | 2 +- .../{side-by-side.js => check-translation.js} | 16 ++++++++++------ .agents/skills/translate/pages.md | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) rename .agents/skills/translate/{side-by-side.js => check-translation.js} (94%) diff --git a/.agents/skills/review-translation/SKILL.md b/.agents/skills/review-translation/SKILL.md index d1eaee2cd89a..6eee7c3bb22c 100644 --- a/.agents/skills/review-translation/SKILL.md +++ b/.agents/skills/review-translation/SKILL.md @@ -40,7 +40,7 @@ reading for them: - Run the check: ```bash - node .agents/skills/translate/side-by-side.js docs/.md... --check + node .agents/skills/translate/check-translation.js docs/.md... ``` Each block it lists is a candidate, not a verdict: read it against the diff --git a/.agents/skills/translate/side-by-side.js b/.agents/skills/translate/check-translation.js similarity index 94% rename from .agents/skills/translate/side-by-side.js rename to .agents/skills/translate/check-translation.js index c3397bb2322f..257668759175 100644 --- a/.agents/skills/translate/side-by-side.js +++ b/.agents/skills/translate/check-translation.js @@ -1,7 +1,7 @@ #!/usr/bin/env node // Checks translated pages against the English they were translated from. // -// node .agents/skills/translate/side-by-side.js es docs/get-started/*.md --check +// node .agents/skills/translate/check-translation.js es docs/get-started/*.md // // Lists blocks where a fixed glossary term appears fewer times in the // translation than in the English, which can mean it was translated. It also @@ -152,7 +152,8 @@ async function blocks(text, filepath) { async function page(locale, enPath) { const rel = enPath.replace(/^docs\//, ''); const esPath = `i18n/${locale}/docusaurus-plugin-content-docs/current/${rel}`; - if (!fs.existsSync(esPath)) return { page: rel, error: 'No translation yet.' }; + if (!fs.existsSync(esPath)) + return { page: rel, error: 'No translation yet.' }; // Check against the English the page was translated from. const esText = fs.readFileSync(esPath, 'utf8'); @@ -160,7 +161,10 @@ async function page(locale, enPath) { /^translation_source_hash: *(.*)$/m ) || [])[1]; if (!hash) - return { page: rel, error: 'No translation_source_hash in the front matter.' }; + return { + page: rel, + error: 'No translation_source_hash in the front matter.', + }; let enBlocks; try { const english = execFileSync('git', ['cat-file', '-p', hash], { @@ -203,9 +207,9 @@ async function page(locale, enPath) { async function main() { const args = process.argv.slice(2); - const [locale, ...files] = args.filter(a => !a.startsWith('--')); - if (!locale || !files.length || !args.includes('--check')) { - console.error('Usage: side-by-side.js ... --check'); + const [locale, ...files] = args; + if (!locale || !files.length) { + console.error('Usage: check-translation.js ...'); process.exit(1); } const flat = s => s.replace(/\s+/g, ' '); diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 535005b7d443..3d9ad7826e68 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -134,7 +134,7 @@ These are on top of the translation rules in `SKILL.md`. ## Check each page ```bash -node .agents/skills/translate/side-by-side.js docs/.md... --check +node .agents/skills/translate/check-translation.js docs/.md... ``` This lists blocks where a fixed glossary term appears fewer times in the From bc041936fde359288999647775f662b63e8663ba Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Mon, 5 Oct 2026 15:36:20 +0100 Subject: [PATCH 31/34] Note in AGENTS.md that the build covers every locale --- AGENTS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 339157d2ff43..36ee27b559c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -98,6 +98,10 @@ yarn generate-adaptors yarn build ``` +`yarn build` builds every locale, so an English change can break a translated +page, for example by moving a page it links to. Don't add `--locale` to speed it +up. + Broken-anchor warnings do not fail the build, and `main` already has some. Fix only the ones your change adds. From 73969a9cd4d4cd73be0d66b1dda0778a6dd0d277 Mon Sep 17 00:00:00 2001 From: Lucy Macartney <64803272+lmac-1@users.noreply.github.com> Date: Tue, 6 Oct 2026 10:43:16 +0100 Subject: [PATCH 32/34] Translate docs into Spanish using our /translate skill (#884) Translate the interface text and these sections into Spanish: Get Started, tutorials, Design Workflows, Write Jobs, Platform, Build, CLI, deploy, get-help, contribute and unit testing. - Target neutral Latin American Spanish (es-419), and add house style rules to es.md from each review-translation pass. - Translate only the blocks whose English changed. Add check-translation.js, which shows the old translation and a word diff for each changed block. - Show a banner on non-Englishnglish. --- .agents/skills/translate/es.md | 15 + docs/build-for-developers/cli-usage.md | 8 +- docs/jobs/best-practices.md | 4 +- docs/jobs/unit-testing-jobs.md | 10 +- docs/tutorials/tutorial.md | 2 +- i18n/es/code.json | 86 ++ .../options.json | 14 + .../options.json | 14 + .../current.json | 62 ++ .../build-for-developers/cli-challenges.md | 275 ++++++ .../build-for-developers/cli-collections.md | 315 ++++++ .../current/build-for-developers/cli-intro.md | 157 +++ .../current/build-for-developers/cli-sync.md | 539 +++++++++++ .../current/build-for-developers/cli-usage.md | 293 ++++++ .../build-for-developers/cli-walkthrough.md | 902 ++++++++++++++++++ .../build-for-developers/security-for-devs.md | 198 ++++ .../current/build/ai-assistant.md | 165 ++++ .../current/build/channels.md | 263 +++++ .../current/build/collections.md | 207 ++++ .../current/build/credentials.md | 142 +++ .../current/build/editing-locally.md | 79 ++ .../current/build/limits.md | 87 ++ .../current/build/paths.md | 69 ++ .../current/build/sandboxes.md | 327 +++++++ .../current/build/steps/step-design-intro.md | 90 ++ .../current/build/steps/step-editor.md | 61 ++ .../current/build/steps/steps.md | 156 +++ .../current/build/triggers.md | 276 ++++++ .../current/build/troubleshooting.md | 143 +++ .../current/build/workflow-snapshots.md | 103 ++ .../current/build/workflows-api.md | 173 ++++ .../current/build/workflows.md | 214 +++++ .../current/build/working-with-branches.md | 63 ++ .../current/contribute/impact-tracker.md | 77 ++ .../current/contribute/roadmap.md | 194 ++++ .../current/contribute/style-guide.md | 262 +++++ .../current/contribute/writing-code.md | 34 + .../current/contribute/writing-docs.md | 67 ++ .../current/deploy/options.md | 119 +++ .../current/deploy/portability-v3.md | 447 +++++++++ .../current/deploy/portability-versions.md | 116 +++ .../current/deploy/portability.md | 197 ++++ .../current/deploy/requirements.md | 170 ++++ .../current/design/api-discovery.md | 158 +++ .../current/design/design-overview.md | 66 ++ .../current/design/design-workflow.md | 75 ++ .../current/design/discovery.md | 173 ++++ .../current/design/mapping-specs.md | 181 ++++ .../current/design/workflow-specs.md | 34 + .../current/get-help/support.md | 31 + .../current/get-started/glossary.md | 180 ++++ .../current/get-started/home.md | 151 +++ .../get-started/implementation-checklist.md | 127 +++ .../get-started/security-compliance.md | 140 +++ .../current/get-started/security.md | 122 +++ .../current/get-started/standards.md | 235 +++++ .../current/get-started/terminology.md | 310 ++++++ .../current/get-started/try-out.md | 60 ++ .../current/hosted/overview.md | 173 ++++ .../current/jobs/best-practices.md | 108 +++ .../current/jobs/compilation.md | 53 + .../current/jobs/data-transformation.md | 199 ++++ .../current/jobs/image-handling.md | 118 +++ .../current/jobs/javascript.md | 331 +++++++ .../current/jobs/job-examples.md | 424 ++++++++ .../current/jobs/job-snippets.md | 131 +++ .../current/jobs/job-writing-guide.md | 75 ++ .../current/jobs/lazy-state-operator.md | 181 ++++ .../current/jobs/operations.md | 405 ++++++++ .../current/jobs/state.md | 229 +++++ .../current/jobs/unit-testing-jobs.md | 226 +++++ .../current/jobs/using-cursors.md | 205 ++++ .../current/keyboard-shortcuts.md | 44 + .../current/manage-projects/collaboration.md | 80 ++ .../manage-projects/io-data-storage.md | 105 ++ .../current/manage-projects/link-to-gh.md | 334 +++++++ .../manage-projects/manage-credentials.md | 189 ++++ .../current/manage-projects/notifications.md | 46 + .../current/manage-projects/oauth.md | 164 ++++ .../current/manage-projects/platform-mgmt.md | 96 ++ .../manage-projects/retention-periods.md | 35 + .../current/manage-projects/staging-prod.md | 126 +++ .../manage-projects/user-roles-permissions.md | 59 ++ .../current/manage-projects/webhook-auth.md | 77 ++ .../manage-projects/workflow-dashboard.md | 27 + .../current/manage-users/api-tokens.md | 36 + .../current/manage-users/user-credentials.md | 55 ++ .../current/manage-users/user-profile.md | 69 ++ .../monitor-history/activity-history.md | 93 ++ .../current/monitor-history/inspect-runs.md | 31 + .../monitor-history/rerunning-workflow.md | 110 +++ .../current/monitor-history/status-codes.md | 58 ++ .../monitor-history/troubleshooting.md | 181 ++++ .../current/tutorials/commcare-to-db.md | 195 ++++ .../current/tutorials/http-to-googlesheets.md | 177 ++++ .../current/tutorials/kobo-to-dhis2.md | 226 +++++ .../current/tutorials/tutorial.md | 43 + i18n/es/docusaurus-theme-classic/footer.json | 42 + i18n/es/docusaurus-theme-classic/navbar.json | 22 + src/components/UntranslatedNotice.js | 27 + src/theme/BlogPostItem/index.js | 13 + src/theme/BlogPostItems/index.js | 19 + src/theme/DocItem/Content/index.js | 16 + translation-rules.yml | 87 ++ 104 files changed, 14966 insertions(+), 12 deletions(-) create mode 100644 i18n/es/code.json create mode 100644 i18n/es/docusaurus-plugin-content-blog-articles/options.json create mode 100644 i18n/es/docusaurus-plugin-content-blog/options.json create mode 100644 i18n/es/docusaurus-plugin-content-docs/current.json create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-challenges.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-collections.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-intro.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-sync.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-usage.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-walkthrough.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/security-for-devs.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/ai-assistant.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/channels.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/collections.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/credentials.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/editing-locally.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/limits.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/paths.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/sandboxes.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-design-intro.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-editor.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/steps/steps.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/troubleshooting.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/workflow-snapshots.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/workflows-api.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/workflows.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/build/working-with-branches.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/contribute/impact-tracker.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/contribute/style-guide.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-code.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-docs.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/deploy/options.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-v3.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-versions.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/deploy/portability.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/deploy/requirements.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/design/api-discovery.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/design/design-overview.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/design/design-workflow.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/design/discovery.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/design/mapping-specs.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/design/workflow-specs.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-help/support.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/glossary.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/home.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/implementation-checklist.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/security-compliance.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/security.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/standards.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/terminology.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/get-started/try-out.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/hosted/overview.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/best-practices.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/compilation.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/data-transformation.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/image-handling.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/javascript.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/job-examples.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/job-snippets.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/job-writing-guide.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/lazy-state-operator.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/operations.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/state.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/unit-testing-jobs.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/jobs/using-cursors.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/keyboard-shortcuts.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/collaboration.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/io-data-storage.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/link-to-gh.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/manage-credentials.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/notifications.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/oauth.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/platform-mgmt.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/retention-periods.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/staging-prod.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/user-roles-permissions.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/webhook-auth.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-projects/workflow-dashboard.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-users/api-tokens.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-credentials.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-profile.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/monitor-history/activity-history.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/monitor-history/inspect-runs.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/monitor-history/rerunning-workflow.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/monitor-history/status-codes.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/monitor-history/troubleshooting.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/tutorials/commcare-to-db.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/tutorials/http-to-googlesheets.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/tutorials/kobo-to-dhis2.md create mode 100644 i18n/es/docusaurus-plugin-content-docs/current/tutorials/tutorial.md create mode 100644 i18n/es/docusaurus-theme-classic/footer.json create mode 100644 i18n/es/docusaurus-theme-classic/navbar.json create mode 100644 src/components/UntranslatedNotice.js create mode 100644 src/theme/BlogPostItem/index.js create mode 100644 src/theme/BlogPostItems/index.js create mode 100644 src/theme/DocItem/Content/index.js diff --git a/.agents/skills/translate/es.md b/.agents/skills/translate/es.md index 8996a1ae1b6e..5e9cb8cb3641 100644 --- a/.agents/skills/translate/es.md +++ b/.agents/skills/translate/es.md @@ -12,6 +12,9 @@ Avoid slang from any one country. ("podrías crear"). - When an English example switches to "I" or "we", keep it addressed to the reader. +- Keep how strong the English is. "Must" is "debe" or "tienes que", "should" is + "debería" or "deberías", and "can" is "puede" or "puedes". Do not write "debe" + for "should". | Use | Not | | ---------------------------------- | --------------------- | @@ -25,6 +28,11 @@ Avoid slang from any one country. | actualmente (currently) | ahora mismo | | considerar | plantearse | | por ejemplo, or como before a list | p. ej. | +| en campo (in the field) | en terreno | +| intentar (try to) | probar a | +| entrada (input) | input | + +Keep `Input` when it names the panel in the app. "Payload" stays in English. ## English terms kept in Spanish @@ -39,6 +47,13 @@ English plural with "-s". For example, "The work order creates two runs" becomes "La work order crea dos runs". +Before a kept English term, choose "y" or "e", and "o" or "u", by how the +English word sounds: "workflows y History", "Canvas e Inspector". + +In a section that defines terms, you may explain a glossary term once in +brackets, with the English first: "Workflow (flujo de trabajo)". Everywhere +else, use the English term on its own. + ## Capitals Follow Spanish capitalization, not the English. Spanish capitalizes far less, diff --git a/docs/build-for-developers/cli-usage.md b/docs/build-for-developers/cli-usage.md index d3acc62a5455..0cf56f75d2c1 100644 --- a/docs/build-for-developers/cli-usage.md +++ b/docs/build-for-developers/cli-usage.md @@ -234,7 +234,7 @@ So you want to write unit tests against your job code? See the full guide. Unit tests only work against pure functions in your code (top level function -declarations): they do not qork against operations or adaptor functions because +declarations): they do not work against operations or adaptor functions because they require connected backend services. To unit test functions in your job code: @@ -253,8 +253,8 @@ openfn compile --exports-only Compiled files are written to `dist/` as `.mjs` files. -With `--exports-only` Operations are stripped out entirely, leaving only -exported functions and variables . +With `--exports-only`, operations are stripped out entirely, leaving only +exported functions and variables. **Compile a single workflow by name:** @@ -262,7 +262,7 @@ exported functions and variables . openfn compile my-workflow --exports-only ``` -**Recompile whenever a job code changes:** +**Recompile whenever job code changes:** ```bash openfn compile --exports-only --watch diff --git a/docs/jobs/best-practices.md b/docs/jobs/best-practices.md index e26dc1e3327d..db347090195e 100644 --- a/docs/jobs/best-practices.md +++ b/docs/jobs/best-practices.md @@ -102,5 +102,5 @@ fn(state => ({ })); ``` -To test the export helpers -[See Unit testing jobs](/documentation/jobs/unit-testing-jobs) +To test the exported helpers, see +[Unit testing jobs](/documentation/jobs/unit-testing-jobs). diff --git a/docs/jobs/unit-testing-jobs.md b/docs/jobs/unit-testing-jobs.md index cef30767d666..10c76a4fa251 100644 --- a/docs/jobs/unit-testing-jobs.md +++ b/docs/jobs/unit-testing-jobs.md @@ -8,7 +8,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; Most job code goes like this: fetch some records, reshape them, send them somewhere else. But the reshaping bit often grows into complex logic - parsing a string into a structured record, mapping local codes onto DHIS2 data elements, -normalising a dozen date formats into one. +normalizing a dozen date formats into one. Unit testing that logic helps to validate that the code runs correctly, and helps to prevent errors occurring when the code is modified later. @@ -114,11 +114,11 @@ avoids the warning without touching your `package.json`. ::: - + - ```js title="workflows/sms-parser/parse-message.js" - export const FIELDS = ['id','name', 'dob', 'weight']; + ```js title="workflows/sms-parser/parse-message.js" + export const FIELDS = ['id', 'name', 'dob', 'weight']; export const parseSms = text => { const parts = text.trim().split('#'); @@ -135,7 +135,7 @@ avoids the warning without touching your `package.json`. ``` - + After `openfn compile --exports-only`: ```js title="dist/sms-parser/parse-message.mjs" diff --git a/docs/tutorials/tutorial.md b/docs/tutorials/tutorial.md index 00443ad3e680..ee5eee9bd2e1 100644 --- a/docs/tutorials/tutorial.md +++ b/docs/tutorials/tutorial.md @@ -20,7 +20,7 @@ sidebar_label: Workflow QuickStart workflow 6. In the `Input` panel on the left, add a custom input (e.g., a payload from a webhook request) or simply add empty brackets (`{}`) to run a Workflow with a - cron trigger. See the [Workflow docs](docs/build/workflows.md) for help with + cron trigger. See the [Workflow docs](/build/workflows.md) for help with running and testing Workflow. 7. If the Step suceeds, navigate back to the Canvase view and click the `+` icon to add a second Step. diff --git a/i18n/es/code.json b/i18n/es/code.json new file mode 100644 index 000000000000..5375475f9971 --- /dev/null +++ b/i18n/es/code.json @@ -0,0 +1,86 @@ +{ + "homepage.highlights.jobWritingGuide.title": { + "message": "Guía para escribir jobs" + }, + "homepage.highlights.jobWritingGuide.description": { + "message": "¿Vas a escribir un job para OpenFn? Empieza aquí" + }, + "homepage.highlights.cliUsage.title": { + "message": "Ejemplos de uso de la CLI" + }, + "homepage.highlights.cliUsage.description": { + "message": "Mira de un vistazo lo que puede hacer la CLI" + }, + "homepage.highlights.javascriptTips.title": { + "message": "Trucos y consejos de JavaScript" + }, + "homepage.highlights.javascriptTips.description": { + "message": "Mejora tu código" + }, + "homepage.features.docs.title": { + "message": "Documentación" + }, + "homepage.features.docs.description": { + "message": "Documentación sobre todos los aspectos de OpenFn, el bien público digital líder en automatización de flujos de trabajo." + }, + "homepage.features.adaptors.title": { + "message": "Adaptors" + }, + "homepage.features.adaptors.description": { + "message": "Documentación, ejemplos, registros de cambios y resúmenes de los adaptors, fáciles de buscar y explorar, para conectar los DPG más usados del mundo." + }, + "homepage.features.articles.title": { + "message": "Artículos" + }, + "homepage.features.articles.description": { + "message": "¿Cómo prepararse para una integración de datos? ¿Cómo estructurar los ID externos? ¿Cómo...?" + }, + "homepage.features.blog.title": { + "message": "Blog" + }, + "homepage.features.blog.description": { + "message": "Ayudamos a las intervenciones de impacto social más prometedoras del mundo a crecer mediante la automatización, la integración de datos y la interoperabilidad. Estas son sus historias." + }, + "homepage.features.enterprise.title": { + "message": "Empresas" + }, + "homepage.features.enterprise.description": { + "message": "Descubre la plataforma de integración como servicio (iPaaS) de OpenFn para empresas, con planes gratuitos para siempre y opciones asequibles para crecer." + }, + "homepage.meta.title": { + "message": "Inicio" + }, + "homepage.meta.description": { + "message": "El sitio de documentación de OpenFn" + }, + "homepage.hero.title": { + "message": "Documentación de OpenFn" + }, + "homepage.hero.subtitle": { + "message": "OpenFn, el bien público digital líder en automatización de flujos de trabajo, hace que ICT4D sea más eficiente." + }, + "homepage.hero.cta": { + "message": "Comenzar" + }, + "homepage.newsletter.imageAlt": { + "message": "Boletín" + }, + "homepage.newsletter.title": { + "message": "Boletín" + }, + "homepage.newsletter.description": { + "message": "No te pierdas ninguna de nuestras historias: suscríbete a nuestro boletín aquí." + }, + "homepage.newsletter.emailPlaceholder": { + "message": "Correo electrónico" + }, + "homepage.newsletter.subscribe": { + "message": "Suscribirse" + }, + "homepage.highlights.heading": { + "message": "✨Destacados de la documentación✨" + }, + "untranslatedNotice.message": { + "message": "Esta página no tiene versión en español, así que te la mostramos en inglés." + } +} diff --git a/i18n/es/docusaurus-plugin-content-blog-articles/options.json b/i18n/es/docusaurus-plugin-content-blog-articles/options.json new file mode 100644 index 000000000000..18375b3a3f5a --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-blog-articles/options.json @@ -0,0 +1,14 @@ +{ + "title": { + "message": "Blog", + "description": "The title for the blog used in SEO" + }, + "description": { + "message": "Blog", + "description": "The description for the blog used in SEO" + }, + "sidebar.title": { + "message": "Publicaciones recientes", + "description": "The label for the left sidebar" + } +} diff --git a/i18n/es/docusaurus-plugin-content-blog/options.json b/i18n/es/docusaurus-plugin-content-blog/options.json new file mode 100644 index 000000000000..18375b3a3f5a --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-blog/options.json @@ -0,0 +1,14 @@ +{ + "title": { + "message": "Blog", + "description": "The title for the blog used in SEO" + }, + "description": { + "message": "Blog", + "description": "The description for the blog used in SEO" + }, + "sidebar.title": { + "message": "Publicaciones recientes", + "description": "The label for the left sidebar" + } +} diff --git a/i18n/es/docusaurus-plugin-content-docs/current.json b/i18n/es/docusaurus-plugin-content-docs/current.json new file mode 100644 index 000000000000..0c458c6b9742 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,62 @@ +{ + "version.label": { + "message": "v2 ⚡", + "description": "The label for version current" + }, + "sidebar.docs.category.Get Started": { + "message": "Primeros pasos", + "description": "The label for category 'Get Started' in sidebar 'docs'" + }, + "sidebar.docs.category.Tutorials": { + "message": "Tutoriales", + "description": "The label for category 'Tutorials' in sidebar 'docs'" + }, + "sidebar.docs.category.Design Workflows": { + "message": "Diseñar workflows", + "description": "The label for category 'Design Workflows' in sidebar 'docs'" + }, + "sidebar.docs.category.Write Jobs": { + "message": "Escribir jobs", + "description": "The label for category 'Write Jobs' in sidebar 'docs'" + }, + "sidebar.docs.category.Platform ⚡": { + "message": "Plataforma ⚡", + "description": "The label for category 'Platform ⚡' in sidebar 'docs'" + }, + "sidebar.docs.category.Build & Manage Workflows": { + "message": "Crear y gestionar workflows", + "description": "The label for category 'Build & Manage Workflows' in sidebar 'docs'" + }, + "sidebar.docs.category.Monitor History": { + "message": "Supervisar el historial", + "description": "The label for category 'Monitor History' in sidebar 'docs'" + }, + "sidebar.docs.category.Manage Projects": { + "message": "Gestionar proyectos", + "description": "The label for category 'Manage Projects' in sidebar 'docs'" + }, + "sidebar.docs.category.Manage Users & Credentials": { + "message": "Gestionar usuarios y credenciales", + "description": "The label for category 'Manage Users & Credentials' in sidebar 'docs'" + }, + "sidebar.docs.category.CLI": { + "message": "CLI", + "description": "The label for category 'CLI' in sidebar 'docs'" + }, + "sidebar.docs.category.Deployment": { + "message": "Despliegue", + "description": "The label for category 'Deployment' in sidebar 'docs'" + }, + "sidebar.docs.category.Migrate to v2": { + "message": "Migrar a v2", + "description": "The label for category 'Migrate to v2' in sidebar 'docs'" + }, + "sidebar.docs.category.Contribute": { + "message": "Contribuir", + "description": "The label for category 'Contribute' in sidebar 'docs'" + }, + "sidebar.docs.link.Community Forum": { + "message": "Foro de la comunidad", + "description": "The label for link 'Community Forum' in sidebar 'docs', linking to 'https://community.openfn.org'" + } +} diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-challenges.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-challenges.md new file mode 100644 index 000000000000..e35c7b195a47 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-challenges.md @@ -0,0 +1,275 @@ +--- +title: Desafíos de la CLI +sidebar_label: Desafíos de la CLI +slug: /cli-challenges +translation_source_hash: 77369268e959643bb1252b91b59cf8176063c423 +translation_review_status: machine +--- + +#### Resuelve problemas reales y demuestra tus habilidades con la línea de comandos participando en nuestros desafíos de la CLI {#solve-real-world-problems-and-showcase-your-command-line-skills-by-participating-in-our-cli-challenges} + +:::tip Notas importantes + +- Un desarrollador con algo de experiencia en JavaScript debería poder escribir, + ejecutar y depurar jobs complejos de varios pasos con OpenFn, usando solo un + editor de texto y su terminal. +- Si te quedas atascado y necesitas ayuda, publica en + [community.openfn.org](https://community.openfn.org). +

+ Expande para ver la plantilla de reporte de errores + + ```markdown + Subject: Bug Report - [Brief Description] + + **Description:** [Concise description of the bug.] + + **Steps to Reproduce:** + + 1. + 2. + 3. + + **Environment:** + + - OS: [e.g., Windows 10] + - CLI: [e.g., v0.4.11] + - Node: [e.g., v 18.17.1] + - NPM: [e.g., 8.19.2] + + **Attachments:** [Screenshots, error messages, or relevant files.] + ``` + +
+ +::: + +### 🏆 Crea un saludo personalizado {#-create-personalized-greeting} + +**Descripción general:** + +Crea un nuevo job `hello.js` que muestre un saludo personalizado con tu nombre. + +**Objetivo:** + +Escribe un job de OpenFn con el [adaptor common](/adaptors/packages/common-docs) +que muestre un mensaje de saludo con tu nombre. + +**Requisitos:** + +1. Instala la última versión del adaptor common. + + ``` + openfn repo install @openfn/language-common + ``` + +**Tareas:** + +1. Crea un archivo nuevo llamado `hello.js`. +2. Escribe un script de JavaScript en `hello.js` que genere un saludo con tu + nombre. +3. Ejecuta el job con el comando `openfn hello.js -a common -o tmp/output.json`. +4. Confirma que se ejecutó correctamente. + +**Lista de verificación:** + +- [ ] Creaste el archivo nuevo `hello.js`. +- [ ] Escribiste en `hello.js` un script de JavaScript para un saludo + personalizado. +- [ ] Ejecutaste el job con el comando indicado. +- [ ] Comprobaste que los logs de la salida de la CLI son correctos. + +--- + +### 🏆 Obtén e inspecciona datos por HTTP {#-fetch-and-inspect-data-via-http} + +**Descripción general:** + +Escribe un job que obtenga datos de usuarios de la +[API de JSONPlaceholder](https://jsonplaceholder.typicode.com/users) con el +[adaptor http](/adaptors/packages/http-docs) de OpenFn. + +**Objetivo:** + +Obtén y muestra los detalles del primer usuario de la API de JSONPlaceholder. + +**Requisitos:** + +1. Instala la última versión del adaptor http. + +```bash +openfn repo install @openfn/language-http +``` + +2. Usa la [API de JSONPlaceholder](https://jsonplaceholder.typicode.com/users). +3. Crea un archivo llamado `getUsers.js` que contenga el script. + +**Tareas:** + +1. Crea un archivo (`getUsers.js`) para el script. +2. Obtén una lista de usuarios de la API de JSONPlaceholder. +3. Muestra los detalles del primer usuario. +4. Ejecuta el job con OpenFn/cli: + `openfn getUsers.js -a http -o tmp/output.json`. +5. Comprueba que los logs de la CLI son los esperados. + +**Lista de verificación:** + +- [ ] Obtuviste los datos de los usuarios. +- [ ] Mostraste correctamente los detalles del primer usuario. +- [ ] Usaste correctamente las funciones del adaptor http de OpenFn. +- [ ] Comprobaste que los logs de la salida de la CLI son correctos. + +--- + +### 🏆 Obtén metadatos de COVID-19 {#-retrieve-covid-19-metadata} + +**Descripción general:** + +Obtén y presenta metadatos de COVID-19 con la +[API del COVID Tracking Project de The Atlantic](https://covidtracking.com/data/api). + +**Objetivo:** + +Escribe un job que obtenga datos de COVID-19 de la API y calcule algunos valores +agregados para el período que elijas. + +**Requisitos:** + +1. Instala la última versión del adaptor http. + +```bash +openfn repo install @openfn/language-http +``` + +**Tareas:** + +1. Escribe una operación de OpenFn que obtenga metadatos de COVID-19 de la + [API del COVID Tracking Project de The Atlantic](https://covidtracking.com/data/api). + - Usa `https://api.covidtracking.com` como tu **baseUrl** en + `state.configuration`. +2. Ejecuta el job con la CLI de OpenFn mediante el comando + `openfn your_operation_file.js -a http -o tmp/output.json`. +3. Evalúa la salida y prueba distintas formas de dar formato a los datos de + COVID-19 por región o de presentarlos. + +**Lista de verificación:** + +- [ ] Creaste un archivo de operación de OpenFn. +- [ ] Escribiste el código para obtener metadatos de COVID-19 de la API + indicada. +- [ ] Ejecutaste el job con el comando de la CLI indicado. +- [ ] Probaste varias opciones de formato o presentación para los datos + obtenidos. + +> Experimenta con la presentación de los datos para entenderlos mejor. ¡Buena +> suerte! 🌐🦠 + +--- + +### 🏆 Extrae nombres y correos electrónicos {#-extract-names--emails} + +**Descripción general:** + +En este desafío, usarás la API de JSONPlaceholder para obtener los comentarios +de una publicación concreta (la publicación con ID 1). Tu tarea es extraer los +campos "name" y "email" de cada comentario y registrar en el log los datos +extraídos. + +**Objetivo:** + +Escribe un job que obtenga los comentarios de la publicación con ID 1, extraiga +los campos "name" y "email" de cada comentario y registre en el log los datos +extraídos. + +**Requisitos:** + +- Conocimientos básicos de JavaScript. +- La CLI de OpenFn instalada en tu computadora. + +**Tareas:** + +1. **Obtén los comentarios de la publicación:** + + - Agrega una operación que obtenga todos los comentarios de la publicación + con ID 1 de la + [API de JSONPlaceholder](https://jsonplaceholder.typicode.com/posts/1/comments). + +2. **Extrae el nombre y el correo electrónico:** + + - Escribe una función que extraiga los campos "name" y "email" de cada + comentario. + +3. **Registra los datos extraídos:** + - Muestra en la consola los datos extraídos (nombre y correo electrónico) de + cada comentario. + +**Lista de verificación:** + +- [ ] Obtuviste los comentarios de la publicación con ID 1. +- [ ] Escribiste una función que extrae "name" y "email" de los comentarios. +- [ ] Mostraste en la consola los datos extraídos. + +--- + +### 🏆 Controla los mensajes de error {#-control-error-messages} + +Depura qué causa un error en la siguiente línea de código y muestra el mensaje +de error. + +```jsx +// Get post where id is 180 +get('posts/180'); +``` + +--- + +### 🏆 Transformación y limpieza de datos {#-data-transformation-and-cleaning} + +**Descripción general:** + +En este desafío, usarás métodos globales de arrays de JavaScript, en concreto +`Array.reduce`, `Array.filter` o `Array.map`, para crear una serie de +operaciones que obtengan publicaciones y las filtren por ID de usuario. + +**Objetivo:** + +Escribe un job que obtenga las publicaciones de un ID de usuario concreto, `1`. + +**Requisitos:** + +1. Usa la API de JSONPlaceholder `https://jsonplaceholder.typicode.com`. +2. Instala la última versión del adaptor http. + +``` +openfn repo install @openfn/language-http +``` + +**Tareas:** + +1. **Crea el archivo:** + + - Crea un archivo llamado `getPosts.js` para tu job. + +2. **Obtén todas las publicaciones:** + + - Agrega la primera operación para obtener todas las publicaciones. Usa la + API indicada o cualquier otra fuente que elijas que ofrezca una lista de + publicaciones. + +3. **Filtra las publicaciones por ID:** + + - Agrega una segunda operación con una función que filtre las publicaciones + por ID de usuario. Puedes usar `Array.filter` o cualquier otro método + adecuado. + +4. **Obtén las publicaciones del ID de usuario 1:** + + - Usa la función de la segunda operación para filtrar las publicaciones del + ID de usuario 1. + +**Lista de verificación:** + +- [ ] Creaste el archivo `getPosts.js`. +- [ ] Obtuviste todas las publicaciones. +- [ ] Escribiste una función que filtra publicaciones por ID de usuario. +- [ ] Obtuviste las publicaciones del ID de usuario 1. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-collections.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-collections.md new file mode 100644 index 000000000000..bda63281f9bb --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-collections.md @@ -0,0 +1,315 @@ +--- +title: Uso de colecciones con la CLI +sidebar_label: Colecciones +slug: /collections-cli +translation_source_hash: da22d0b4b6f41876daff9101ed14f28cd796084d +translation_review_status: machine +--- + +La CLI de OpenFn permite leer y escribir en +[colecciones](/build/collections.md): un almacén de clave/valor integrado en +OpenFn. + +:::caution Versiones + +La compatibilidad con colecciones se agregó a la CLI en la versión 1.9.0. + +Ejecuta `npm install -g @openfn/cli` para actualizarla o instalarla. + +::: + +Puedes usar la CLI para: + +- Explorar el contenido de las colecciones sin ejecutar un workflow +- Probar la sintaxis de consulta para obtener las claves que necesitas +- Actualizar objetos de mapeo y tablas de búsqueda a partir de archivos locales + (o bajo control de versiones) +- Eliminar datos manualmente + +:::tip + +¿Tienes comentarios? ¿Quieres más compatibilidad con colecciones en la CLI? +¡Publica una solicitud de funcionalidad en +[community.openfn.org](https://community.openfn.org/c/feature-requests)! + +::: + +Empieza a usar la API de colecciones con `openfn collections --help`. + +Necesitarás un token de acceso personal (PAT) para acceder a una colección. +También tienes que asegurarte de que la colección exista antes de poder leerla o +escribir en ella. Consulta +[Gestionar colecciones](/build/collections.md#managing-collections). + +:::info ¿Quieres usar colecciones en un workflow de la CLI? + +Esta documentación explica cómo usar el comando `openfn collections` de la CLI. + +Si ejecutas una expresión o un workflow con la CLI, tienes que usar el adaptor +de colecciones. Consulta la +[documentación del adaptor de colecciones](/adaptors/collections#cli-usage) para +más detalles + +::: + +## Obtener un PAT {#getting-a-pat} + +Los datos de las colecciones se guardan de forma segura dentro de un proyecto, y +solo pueden acceder a ellos los usuarios con acceso a ese proyecto. Así que, si +quieres acceder a una colección, tienes que decirle al servidor quién eres. + +Para eso usamos los tokens de acceso personal. Consulta +[Crear y administrar tokens de API](/manage-users/api-tokens.md#about-api-tokens) +para más detalles. + +Cuando tengas un PAT, tienes que pasárselo a la CLI. La forma más fácil es +definir la variable de entorno `OPENFN_API_KEY` o usar un archivo `.env`. La CLI +usará este valor automáticamente en todas las solicitudes. + +También puedes pasar `--token` a la CLI para reemplazar el valor cargado de tu +entorno. + +```bash +openfn collections get my-collection \* --token $MY_OPENFN_PAT +``` + +:::tip + +El resto de esta guía da por hecho que la variable de entorno `OPENFN_PAT` está +definida. Si lo está y usas un servidor que tiene una colección `my-collection`, +todos los ejemplos funcionarán. + +::: + +## Definir un servidor {#setting-a-server} + +De forma predeterminada, la CLI apunta a nuestra aplicación en la nube en +https://app.openfn.org. + +Si usas la versión de código abierto u otro despliegue, también tendrás que +indicarle a la CLI qué servidor de colecciones usar. + +Puedes hacerlo pasando `--endpoint` directamente: + +```bash +openfn collections get my-collection \* --endpoint http://localhost:4000 +``` + +O definiendo la variable de entorno `OPENFN_ENDPOINT`. + +:::tip + +Para ver qué servidor está usando la CLI, pide logs de nivel debug en la salida: + +```bash +openfn collections get my-collection \* --log debug +``` + +::: + +## Nombres únicos por proyecto {#project-name-uniqueness} + +En las versiones de Lightning anteriores a la 2.17.0, los nombres de las +colecciones eran únicos en toda la instancia. + +Desde la 2.17.0, los nombres de las colecciones son únicos dentro de un +proyecto, así que una instancia de OpenFn puede tener varias colecciones con el +mismo nombre. + +Cualquier solicitud a la API de colecciones intenta resolver un nombre de +colección a una sola colección. Pero si hay conflictos, el servidor devuelve un +código de error 409. + +Para resolverlo, pasa un id de proyecto: + +```bash +openfn collections get --project-id 1d28c76c-e4ef-4e58-ac1e-464dc479946c +``` + +También puedes definir el ID del proyecto con una variable de entorno, o usar el +atajo `-p`. + +## Obtener elementos {#fetching-items} + +Puedes obtener elementos de una colección pasando un nombre de colección y una +clave, o un patrón de claves (como `*` para "todo", o `2024*` para las claves +que empiezan por `2024`). + +```bash +openfn collections get +``` + +Por ejemplo, para obtener todo de `my-collection`, ejecuta: + +```bash +openfn collections get my-collection \* +``` + +:::tip + +En las terminales de Unix (macOS o Linux), el carácter `*` tiene un significado +especial. Así que, si quieres obtener todos los elementos, tienes que escaparlo +o ponerlo entre comillas: + +``` +openfn collections get my-collection \* +``` + +Incluir `*` en un patrón debería seguir funcionando: + +``` +openfn collections get my-collection 2024* +``` + +::: + +Los elementos de las colecciones se guardan como cadenas, pero se serializan a +JSON en la salida. + +De forma predeterminada, la CLI muestra los valores descargados en tu terminal. +Para escribirlos en disco, pasa `--output` o `-o` con una ruta de archivo +relativa a tu directorio de trabajo: + +```bash +openfn collections get my-collection \* -o /tmp/my_collection.json +``` + +Para dar formato a la salida y que sea más fácil de leer, agrega la opción +`--pretty`: + +```bash +openfn collections get my-collection \* -o /tmp/my_collection.json --pretty +``` + +Es importante entender que la salida funciona un poco distinto si obtienes un +elemento concreto con una sola clave o si obtienes muchos elementos con un +patrón de claves. + +Una sola clave siempre devuelve su valor "en bruto" o "tal cual", sin la clave. +Así que, para una clave `item-1` cuyo valor es un objeto JSON, esto: + +```bash +openfn collections get my-collection item-1 +``` + +Descargará y guardará algo como esto: + +```js +{ + "id": "item-1" + /* ... other properties of the value */ +} +``` + +Si usas un patrón de claves para obtener datos, el valor se devuelve en modo +multielemento: un objeto JSON donde la clave es la clave del elemento y el valor +es el valor del elemento. + +Así que, si obtienes todos los elementos cuya clave empieza por `item-`: + +```bash +$ openfn collections get my-collection item-1* +``` + +Los datos resultantes se verán así: + +```json +{ + "item-1": { + "id": "item-1" + /* ... other properties of the value */ + }, + "item-10": { + "id": "item-10" + /* ... other properties of the value */ + } +} +``` + +## Subir elementos {#uploading-items} + +Puedes usar el comando `collections` para subir datos a una colección. Al subir, +los valores siempre vienen de un archivo en disco. En este ejemplo se usan +archivos JSON, pero si subes un solo valor, no hace falta que sea JSON válido. + +El comando `set` tiene dos modos. Para subir un solo elemento, usa: + +```bash +openfn collections set +``` + +Esto lee los datos de `path/to/value.json` como una cadena y hace un upsert con +la clave indicada. No se admiten patrones de claves. + +Para hacer un upsert masivo de varios valores, usa: + +```bash +openfn collections set --items +``` + +El archivo `items.json` tiene que contener un objeto JSON donde las claves son +las claves de los elementos y los valores son los valores de los elementos +(igual que lo que devuelve el comando get multielemento): + +```json +{ + "item-1": { + "id": "item-1" + /* ... other properties of the value */ + }, + "item-10": { + "id": "item-10" + /* ... other properties of the value */ + } +} +``` + +:::tip + +Recuerda que las colecciones siempre usan una estrategia de _upsert_ al subir +elementos nuevos. + +Es decir, si una clave no existe, se crea y se le asigna un valor. Si ya existe, +se actualiza su valor. + +::: + +## Eliminar elementos {#removing-items} + +También puedes eliminar elementos de una colección con el comando +`collections remove`: + +```bash +openfn collections remove +``` + +Se admiten patrones de claves, que te permiten eliminar varias claves. + +Usa `--dry-run` para obtener una lista de las claves que se eliminarían, sin +eliminarlas realmente: + +```bash +openfn collections remove my-collection 2024* --dry-run +``` + +## Solución de problemas {#troubleshooting} + +### Error 409: varios nombres de colección coinciden {#error-409-multiple-collection-names-matched} + +Esto significa que pediste una colección por su nombre, pero el servidor tiene +varias colecciones con ese nombre. + +Tienes que limitar tu solicitud al proyecto correcto incluyendo el id del +proyecto en la solicitud. + +Puedes pasarlo directamente: + +``` +openfn collections get my-collection \* --project-id 1d28c76c-e4ef-4e58-ac1e-464dc479946c +``` + +O definir una variable de entorno (se admiten archivos `.env`): + +``` +OPENFN_PROJECT_ID=1d28c76c-e4ef-4e58-ac1e-464dc479946c +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-intro.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-intro.md new file mode 100644 index 000000000000..6fb5f7d5bbfb --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-intro.md @@ -0,0 +1,157 @@ +--- +title: Primeros pasos con la CLI de OpenFn +sidebar_label: Primeros pasos +slug: /cli +translation_source_hash: 944be784d36e841ea14f6d763d05830a7da98a18 +translation_review_status: machine +--- + +#### Crea y prueba tus workflows e integraciones automatizadas desde la línea de comandos. {#build-and-test-your-automated-workflows-and-integrations-via-the-command-line} + +La CLI de OpenFn es una herramienta para desarrolladores que te ayuda a crear, +probar y gestionar tus workflows directamente desde la línea de comandos. Es +fácil de instalar, funciona en macOS, Windows y Linux, y ofrece muchas +funcionalidades para mejorar tu experiencia de desarrollo con OpenFn. Con la CLI +de OpenFn puedes: + +- Sincronizar workflows entre OpenFn y un sistema de archivos local o GitHub +- Ejecutar workflows de OpenFn de forma segura +- Diagnosticar y depurar steps de OpenFn +- [Hacer pruebas unitarias del código de los jobs](/documentation/jobs/unit-testing-jobs) + con JavaScript estándar +- Leer y escribir datos de colecciones + +--- + +### Antes de empezar {#before-you-start} + +Antes de empezar con @openfn/cli, prepara algunas herramientas clave: + +1. **Editor de código:** asegúrate de tener un editor de código instalado en tu + computadora. Puedes usar editores populares como + [VS Code](https://code.visualstudio.com/) o + [Sublime](https://www.sublimetext.com/). +2. **Node.js:** instala Node.js (versión 24 o posterior). En Linux, Windows o + macOS, usa un gestor de versiones como [nvm](https://github.com/nvm-sh/nvm) o + [asdf](https://asdf-vm.com/guide/getting-started.html). También puedes + [instalar Node.js directamente](https://kinsta.com/blog/how-to-install-node-js/) + siguiendo esta guía. + +También deberías **entender los conceptos básicos de OpenFn**, en particular los +steps y los adaptors. Consulta la +[sección de introducción](/get-started/home.md) de este sitio para ponerte al +día. + +--- + +### Instalar la CLI {#install-the-cli} + +Para descargar la última versión de +[@openfn/cli](https://www.npmjs.com/package/@openfn/cli), ejecuta el siguiente +comando en la línea de comandos. + +```bash +npm install -g @openfn/cli +``` + +Comprueba que todo funciona ejecutando el workflow de prueba incluido: + +```bash +openfn test +``` + +La palabra `openfn` invoca la CLI. La palabra `test` invoca el comando de +prueba. + +
+Expande para ver la salida esperada + +``` +[CLI] ♦ Versions: + ▸ node.js 18.12.1 + ▸ cli 1.0.0 +[CLI] ℹ Running test workflow... +[CLI] ℹ Execution plan: +[CLI] ℹ { + "options": { + "start": "start" + }, + "workflow": { + "steps": [ + { + "id": "start", + "state": { + "data": { + "defaultAnswer": 42 + } + "expression": "const fn = () => (state) => { console.log('Starting computer...'); return state; }; fn()", + "next": { + "calculate": "!state.error" + } + }, + { + "id": "calculate", + "expression": "const fn = () => (state) => { console.log('Calculating to life, the universe, and everything..'); return state }; fn()", + "next": { + "result": true + } + }, + { + "id": "result", + "expression": "const fn = () => (state) => ({ data: { answer: state.data.answer || state.data.defaultAnswer } }); fn()" + } + ] + } +} + +[CLI] ✔ Compiled all expressions in workflow +[R/T] ℹ Executing undefined +[R/T] ℹ Starting step start +[JOB] ℹ Starting computer... +[R/T] ✔ Completed step start in 1ms +[R/T] ℹ Starting step calculate +[JOB] ℹ Calculating to life, the universe, and everything.. +[R/T] ✔ Completed step calculate in 1ms +[R/T] ℹ Starting step result +[R/T] ✔ Completed step result in 0ms +[CLI] ✔ Result: 42 +``` + +
+ +El resto de la salida es la CLI contándote lo que hace internamente. + +**Consultar la versión** + +```bash +openfn -v +``` + +**Obtener ayuda** + +```bash +openfn help +``` + +--- + +### Actualizar la CLI {#updating-the-cli} + +Para instalar una versión nueva directamente sobre la que tienes instalada, +ejecuta el siguiente comando. + +```bash +npm install -g @openfn/cli +``` + +--- + +### Solución de problemas {#troubleshooting} + +Si tienes problemas con la instalación, intenta desinstalar primero la versión +actual y luego volver a instalarla. + +```bash +npm uninstall -g @openfn/cli +npm install -g @openfn/cli +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-sync.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-sync.md new file mode 100644 index 000000000000..af2cc0b0e9e6 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-sync.md @@ -0,0 +1,539 @@ +--- +title: OpenFn Sync +sidebar_label: Sync +slug: /sync +translation_source_hash: e4dbcbf31dd1975afc33be952424a6476b240f73 +translation_review_status: machine +--- + +Los proyectos de OpenFn son totalmente portables, es decir, se pueden mover a +otros lugares. + +Puedes crear un proyecto en la aplicación, descargarlo a tu computadora para +desarrollar sin conexión, volver a subirlo a la aplicación o incluso desplegarlo +en otro servidor de OpenFn. + +A esto lo llamamos OpenFn Sync, y es una de las funcionalidades más potentes que +ofrecen los proyectos de OpenFn. + +## ¿Qué es un proyecto? {#what-is-a-project} + +Un proyecto es un conjunto de workflows que resuelve, automatiza o integra +alguna función de negocio. + +Un proyecto vive en la aplicación de OpenFn (ya sea en la instancia SaaS en la +nube o en una instancia desplegada de forma privada), pero también puede existir +como archivos en un sistema de archivos. + +Cada proyecto lleva asociados algunos metadatos (como un nombre y una +descripción) y cierta configuración, como credenciales y colecciones. + +Dentro de la aplicación de OpenFn, un proyecto es una entidad de nivel superior +facturable, y todos sus workflows y su configuración se guardan en tablas de la +base de datos. + +El proyecto de la aplicación incluye muchas cosas más: la configuración de +canales, el historial de runs, los dataclips guardados y las sesiones de chat +con el asistente de IA. + +El proyecto también puede existir en un sistema de archivos local. En ese caso, +es un conjunto de archivos que la CLI puede leer y ejecutar. Esta representación +local de un proyecto es bastante básica: aquí solo encontrarás workflows y +código. + +En el sistema de archivos pueden existir a la vez varios proyectos relacionados. +Cada uno vive en un único archivo de proyecto. Puedes hacer "checkout" o +"expandir" un proyecto a la vez en una carpeta local, lo que crea un archivo por +cada workflow y un archivo por cada step. + +Es habitual que un mismo proyecto conceptual (es decir, el código y la +configuración que impulsan una función de negocio) exista en varios lugares a la +vez. Puede tener varias representaciones en la aplicación a través de sandboxes, +tener una copia de seguridad en GitHub, ejecutarse localmente en la computadora +de un desarrollador y distribuirse a varias instancias remotas para ejecutarse +en producción. + +A veces llamamos espacio de trabajo a este conjunto de todos los proyectos +conocidos, relacionados y distribuidos. El problema de la sincronización +consiste en cómo se copian, despliegan o replican el código y la configuración +entre las instancias de un proyecto. + +No todos los elementos de un proyecto se incluyen en una sincronización. Por lo +general, sincronizamos los workflows del proyecto y algunas de sus opciones. +Pero no sincronizamos los datos asociados, los valores de las credenciales, el +historial de uso ni las sesiones de IA. + +## Estructura del proyecto {#project-structure} + +OpenFn Sync escribe un proyecto en el sistema de archivos siguiendo una serie de +convenciones. Tanto si usas la CLI como GitHub Sync, un proyecto tiene la +siguiente estructura: + +``` +├── openfn.yaml +├── .projects +│ ├── main@app.openfn.org.yaml +└── workflows + ├── my-workflow + │ ├── my-workflow.yaml + │ ├── my-step.js +``` + +En resumen, estos archivos son: + +- `openfn.yaml` declara que esta carpeta es un proyecto de OpenFn y contiene + metadatos y ajustes +- La carpeta `.projects` contiene una representación YAML completa de cada + proyecto +- La carpeta `workflows` muestra el contenido del proyecto: los steps, las + conexiones, etc. + +Veamos esta estructura con un poco más de detalle. + +### project.yaml {#projectyaml} + +El archivo de proyecto guarda una copia de todo el estado de un proyecto tal +como está guardado en la aplicación. Si lo abres, verás los workflows +representados como texto plano. + +El nombre de un archivo de proyecto tiene la forma `@.yaml`. El +alias es un nombre local que sirve para referirse a una versión concreta del +proyecto. El dominio es el de la instancia de OpenFn desde la que se descargó el +proyecto. + +No deberías editar el archivo de proyecto localmente, porque cualquier cambio se +perderá la próxima vez que lo obtengas. + +Puedes obtener tantos proyectos como quieras, y cada uno se guardará en su +propio archivo project.yaml. + +La carpeta `.projects` puede y debería incluirse en el control de versiones. + +### workflows {#workflows} + +Tener todo el proyecto dentro de un único archivo no es una buena forma de leer +o editar workflows. Por eso la CLI puede hacer "checkout" o "expandir" un +archivo de proyecto en el sistema de archivos. + +Hacer checkout es el proceso de escribir cada workflow en un archivo +workflow.yaml y cada step en un archivo step.js. Todo esto vive en el directorio +`workflows`. + +Aquí puedes editar los archivos todo lo que quieras, y los cambios se +registrarán cuando subas o despliegues de nuevo a la aplicación. + +Solo puedes hacer checkout de un proyecto a la vez. En realidad, esto viene muy +bien para trabajar con git, porque puedes hacer checkout de dos proyectos en +ramas distintas y compararlos o fusionarlos directamente entre sí. + +### workflow.yaml {#workflowyaml} + +Un archivo workflow.yaml define los steps de un workflow y las conexiones que +los unen. + +``` +id: my-workflow +name: My Workflow +start: webhook +steps: + - id: my-step + name: My Step + adaptor: '@openfn/language-http@7.2.9' + expression: ./my-step.js + - id: webhook + type: webhook + enabled: true + next: + my-step: + disabled: false + condition: always +``` + +La clave `next` de cada step define las conexiones de salida de ese step, es +decir, los steps que se ejecutan a continuación. En el ejemplo anterior, el step +`webhook` se ejecuta primero (lo determina la clave `start`) y define una única +conexión a `my-step`, que se ejecuta siempre. + +El código de cada step vive en su propio archivo .js. Puedes modificar el código +libremente y sincronizarlo de nuevo con el servidor en cualquier momento. Si +quieres cambiar el nombre de un step, asegúrate de actualizar el nombre del +archivo del step y la ruta de la clave `expression` en `workflow.yaml`. + +### openfn.yaml {#openfnyaml} + +Es un archivo de configuración de nivel superior que, en general, puedes +ignorar. Las herramientas de OpenFn lo usan para reconocer la carpeta raíz de un +proyecto. También contiene opciones de configuración para todos los proyectos +locales y metadatos sobre el proyecto que tienes con checkout. + +## Autorización {#authorization} + +Antes de usar la CLI para obtener algo de la aplicación, tendrás que +proporcionar una autorización. + +La mejor forma de hacerlo es definir una variable de entorno llamada +`OPENFN_API_KEY`. Dale el valor de tu +[token de acceso personal](/manage-users/api-tokens.md#about-api-tokens). + +:::info Tokens de acceso personal + +Consulta [Crear y administrar tokens de API](/manage-users/api-tokens.md) para +obtener ayuda con la configuración de un token. + +::: + +Si te conectas a varios proyectos o aplicaciones de OpenFn, puedes crear un +archivo `.env` y definir ahí las variables de entorno que necesites. La CLI +cargará este archivo e indicará qué claves está usando. Los valores de tu +archivo `.env` tienen prioridad sobre los definidos en tu sistema. + +También puedes pasar `--api-key` directamente como opción en la mayoría de los +comandos. + +:::info + +Esta guía da por hecho que quieres sincronizar con nuestra aplicación SaaS +alojada en [app.openfn.org](https://app.openfn.org) + +Puedes sincronizar con otra instancia de OpenFn definiendo la variable de +entorno `OPENFN_ENDPOINT` o pasando el argumento `--endpoint` en la mayoría de +los comandos. + +::: + +## Descargar un proyecto {#downloading-a-project} + +Para descargar un proyecto de la aplicación a tu computadora, ejecuta: + +```bash +openfn project pull +``` + +Esto crea un archivo en tu directorio de trabajo llamado +`.projects/main@app.openfn.org.yaml`. + +:::info + +Cada proyecto de la aplicación tiene un identificador único, llamado UUID, que +sirve para referirse a él. Es un número de 32 dígitos con la forma +`a6cc5bdd-b04f-4413-b4b8-132a5115acac` + +Puedes copiar el UUID de un proyecto desde la URL al abrirlo en la aplicación. +Es la cadena larga que va después de `projects`. + +Por ejemplo, el UUID es la parte en negrita de: +{'https://openfn.org/projects/'}{'abc087dd-3963-4260-8d09-ced2e1ff2bb0'}{'/w'} + +::: + +Después de descargar un proyecto por primera vez, no hace falta volver a indicar +el UUID. Puedes usar el alias, el id o dejar el identificador en blanco para +usar el proyecto que tienes con checkout. + +El comando `project pull` hace tres cosas: + +- Si no tienes un archivo `openfn.yaml`, crea uno +- _Obtiene_ (descarga) tu proyecto de la aplicación y lo guarda en un único + archivo en `.projects/main@app.openfn.org.yaml` +- Hace _checkout_ (expande) de ese proyecto en tu sistema de archivos, con cada + workflow y cada step en su propio archivo. + +## Alias {#aliases} + +En lugar de identificar un proyecto local con un UUID o un id largo, puedes usar +un _alias_. + +Cada proyecto local se guarda en un archivo como `main@app.openfn.org.yaml`, +donde la parte `main` es el alias local del proyecto. + +Puedes descargar un proyecto y definir el alias al mismo tiempo ejecutando: + +```bash +openfn project pull --alias dev +``` + +Esto guarda el proyecto en `dev@app.openfn.org.yaml`. + +Para cambiar el alias, basta con cambiar el nombre del archivo. Lo que vaya +antes de `@` se tratará como el alias. + +## Hacer checkout {#checking-out} + +Puedes hacer checkout de un proyecto en cualquier momento con: + +```bash +openfn project checkout +``` + +Esto actualiza tu carpeta local de workflows con el proyecto indicado. + +Si un checkout va a hacer que se pierdan cambios (es decir, cambiaste un archivo +step.js pero no lo desplegaste), recibirás una advertencia. Agrega `--force` +para ignorar el cambio, o ejecuta `openfn project clean` para borrar y +restablecer la carpeta `workflows`. + +El checkout solo modifica los archivos que gestiona la CLI, básicamente los +archivos de workflows y de steps. Si tienes otros archivos en el sistema de +archivos (como archivos de state o de pruebas), no se tocan. + +## Ejecutar workflows en proyectos {#running-workflows-in-projects} + +Puedes ejecutar cualquier workflow del proyecto con checkout por su nombre: + +```bash +openfn my-workflow +``` + +La CLI busca el workflow en tu carpeta `workflows` y lo ejecuta. Puedes pasar +state con `-s` y definir los niveles de log como de costumbre. + +Al ejecutar un workflow por su nombre de esta forma, obtienes dos ventajas: + +- Las **credenciales** se cargan automáticamente desde el mapa de credenciales + de `openfn.yaml`, así que no necesitas pasar `--credential-map` +- Las **colecciones** usan el servidor configurado en `openfn.yaml`, así que no + necesitas pasar `--collections-endpoint` ni nada más. + +## Desplegar un proyecto {#deploying-a-project} + +Para subir tus cambios locales de nuevo a la aplicación, ejecuta: + +```bash +openfn project deploy +``` + +Esto toma el proyecto que tienes con checkout y lo sube a la aplicación. También +indica qué cambió en el proyecto local. + +Antes de subirlo, la CLI obtiene la última versión del proyecto desde la +aplicación y comprueba si hay **divergencia**, es decir, si alguno de los +workflows que cambiaste localmente también se editó en la aplicación desde la +última vez que lo descargaste. Si es así, el despliegue falla con un error para +evitar que sobrescribas por accidente el trabajo de otra persona. + +Si quieres subirlo de todos modos, pasa `--force`: + +```bash +openfn project deploy --force +``` + +Para ver qué cambiaría sin subir nada, usa `--dry-run`. Esto registra en el log +el payload final de la actualización que se enviaría a la aplicación (como una +estructura JSON). + +Puedes desplegar el proyecto con checkout como un proyecto nuevo en la +aplicación de destino agregando la opción `--new`. Solo está disponible si +tienes privilegios de superusuario en la instancia de destino. + +También puedes desplegar el proyecto con checkout en otro proyecto de la +aplicación pasando su alias, id o uuid: + +``` +openfn project deploy main +``` + +Si tienes un sandbox de desarrollo con checkout, esto lo fusionaría directamente +en el proyecto principal de la aplicación. + +Ten en cuenta que tienes que haber obtenido el proyecto de destino localmente +antes de poder desplegarlo. + +## Despliegue avanzado {#advanced-deployment} + +De forma predeterminada, `openfn project deploy` toma el proyecto con checkout y +lo sube al servidor del que vino originalmente (según lo define el archivo de +proyecto). + +Pero también puedes usar deploy para sincronizar entre proyectos. Normalmente lo +harás para promover un sandbox de desarrollo o de staging a producción. Incluso +puedes usarlo para desplegar un proyecto de la aplicación SaaS en otra instancia +de OpenFn completamente distinta. + +### Desplegar directamente desde un archivo de spec o de state {#deploy-straight-from-a-spec-or-state-file} + +Con cualquier archivo de proyecto (por ejemplo, main@app.openfn.org.yaml) o una +spec exportada (en formato v1 o v2, tal como se exporta desde la configuración +de la aplicación), puedes desplegar directamente en otra instancia sin tener que +hacer checkout de nada antes. + +Solo tienes que pasar el nombre del archivo de origen como primer argumento. Si +tienes acceso de superusuario, puedes crear un proyecto nuevo así: + +``` +openfn project deploy dev@localhost.yaml --new --endpoint https://app.openfn.org +``` + +Si ya tienes un archivo de proyecto registrado localmente (lo tendrás si lo +obtuviste o descargaste antes), puedes pasar el alias (por ejemplo, `main`) para +desplegar en esa instancia. + +``` +openfn project deploy dev@localhost.yaml main +``` + +Lo más probable es que quieras forzar el despliegue, aunque se detecte +divergencia. Para eso, pasa la opción `-f`. + +Pasa `--no-confirm` o `-y` para omitir las preguntas de confirmación (esto es +importante si ejecutas scripts automatizados). + +### Gestionar credenciales {#managing-credentials} + +Gestionar las credenciales durante un despliegue puede ser complicado. + +Las credenciales del proyecto de origen TIENEN que existir en el proyecto de +destino; de lo contrario, se producirá un error. + +Todavía no hay forma de automatizar por completo la creación de credenciales, +porque plantea muchos problemas de seguridad. + +Sin embargo, la CLI ofrece algunas opciones. + +Puedes quitar por completo las credenciales del despliegue pasando +`--credentials none`. Ten en cuenta que el workflow no se ejecutará en el +destino hasta que se conecten manualmente las credenciales a los steps que las +necesitan. + +:::tip + +El argumento `--credentials` se puede pasar como `-c` o `--cred`. + +::: + +También puedes mapear credenciales, si el propietario o el nombre de las +credenciales es distinto en el sistema de destino. + +Puedes hacerlo con la CLI pasando un mapa separado por comas: + +``` +openfn project deploy spec.yaml --credentials a:service@openfn.org|cred-a,b:service@openfn.org|cred-b +``` + +Esto toma dos credenciales, `a` y `b`, las mapea a los nombres `cred-a` y +`cred-b` y cambia el propietario a `service@openfn.org`. + +También puedes definir estos mapeos en un archivo yaml (el mismo que se usa en +la ejecución). Define la clave `alias` debajo del identificador de la +credencial: + +``` +somedev@gmail.com|a: + alias: service@openfn.org|cred-a + +somedev@gmail.com|a: + alias: service@openfn.org|cred-b + +``` + +Luego pasa la ruta del archivo a la CLI con `--credentials`: + +``` +openfn project deploy spec.yaml --credentials credentials.yaml +``` + +## Sandboxes {#sandboxes} + +La CLI es totalmente compatible con los sandboxes. Trátalos como cualquier otro +proyecto: obtenlos la primera vez con su UUID. + +Usa el comando `checkout` para cambiar entre sandboxes y proyectos localmente. +Recuerda que solo puedes tener un proyecto con checkout a la vez. La CLI te +avisará si un checkout va a hacer que pierdas cambios locales. + +Al obtener un sandbox, el alias del proyecto será, de forma predeterminada, el +nombre del sandbox. + +Puedes fusionar dos proyectos localmente con `openfn project merge` y desplegar +el proyecto resultante en la aplicación (probablemente tendrás que forzar la +subida del cambio). Esto es útil para resolver conflictos. + +## Resolver conflictos de fusión {#resolving-merge-conflicts} + +A veces, fusionar un sandbox puede sobrescribir cambios en el proyecto de +destino. Esto puede pasar si un workflow del proyecto principal cambió _después_ +de que se volviera a crear el sandbox, así que el sandbox no lo conoce. Al +fusionar, se perdería ese cambio en main. + +Puedes resolver estos conflictos localmente con la CLI y git (u otro control de +versiones equivalente) y luego subir el proyecto resuelto a la aplicación. + +:::tip + +¡No necesitas un repositorio de GitHub para usar git! + +Git es simplemente un programa que se ejecuta en la terminal de tu sistema +local. + +GitHub es una aplicación alojada en la nube que a) ofrece acceso remoto a +repositorios git y b) ofrece una interfaz completa sobre un sistema de archivos +controlado por git. + +::: + +Así se hace con git. Este ejemplo da por hecho que quieres fusionar un sandbox +`dev` en tu proyecto principal `main`. + +- Asegúrate de tener lista una carpeta local para trabajar. Tiene que ser un + repositorio git. Ejecuta `git init` en cualquier carpeta para configurar git + (no necesitas un repositorio de GitHub conectado) +- Descarga tu proyecto principal localmente: `openfn project pull ` + (da por hecho que OPENFN_API_KEY está definida) +- Haz commit de tus cambios en git: + `git add . && git commit -m "checkout main project"` +- Ahora descarga tu sandbox localmente: `openfn project pull ` +- Esto hace que tu carpeta local `workflows/` se vea como tu sandbox +- Si ahora ejecutas `git status` y `git diff`, verás todos los cambios que se + aplicarían al proyecto principal al hacer la fusión +- Comprueba que estás conforme con las diferencias. Quizás quieras revertir + algunos archivos para que queden como en main + (`git checkout main workflows/my-workflow/job.js`). O quizás quieras combinar + a mano cambios de los dos proyectos. +- Cuando termines, sube el proyecto a la aplicación con la CLI: + `openfn project deploy main` +- Si quieres, puedes hacer commit de tus cambios en git (pero para este ejemplo + no hace falta) + +Para cambios más complejos, puedes probar este enfoque: + +- Descarga `main` localmente y haz commit +- Crea una rama nueva, y descarga `dev` y haz commit en esa rama +- Vuelve a la rama principal: `git checkout main` +- Fusiona la rama `dev` en main: `git merge dev` +- Resuelve los conflictos que indique git (cada conflicto de git debería + corresponder a un lugar donde tanto main como el sandbox hicieron cambios) +- Cuando termines, usa la CLI para forzar el despliegue de tus cambios + +## GitHub {#github} + +Puedes configurar un proyecto para que se sincronice automáticamente con GitHub. +Así, los commits en GitHub despliegan automáticamente los cambios en un proyecto +de OpenFn, y al presionar Save & Sync en la aplicación se hace commit de vuelta +en GitHub. + +Internamente, GitHub Sync usa los comandos `pull` y `deploy` de la CLI, que se +ejecutan desde GitHub Actions, para sincronizar tus proyectos. + +Ten en cuenta que, de forma predeterminada, GitHub Sync usa el formato antiguo, +con los archivos `state.json`, `project.yaml` y `config.json`. Al configurar un +nuevo GitHub Sync, puedes elegir el formato v2. La sincronización v2 solo sirve +para descargar un único proyecto por rama en GitHub, porque varios proyectos +sobrescribirían la misma carpeta `workflows/`. + +Consulta [Control de versiones](/manage-projects/link-to-gh.md) para obtener más +detalles sobre GitHub Sync. + +## Referencia rápida {#cheatsheet} + +| Comando | Descripción | +| ---------------------------------------- | ------------------------------------------------------------------------------------------ | +| `openfn project pull ` | Descarga un proyecto de la aplicación por primera vez | +| `openfn project pull` | Vuelve a descargar el proyecto actual | +| `openfn project pull --alias dev` | Descarga y define un alias local | +| `openfn project fetch ` | Obtiene un proyecto sin hacer checkout | +| `openfn project` | Lista todos los proyectos locales de la carpeta de trabajo actual | +| `openfn project checkout ` | Cambia a otro proyecto local | +| `openfn project deploy` | Despliega en la aplicación el proyecto con checkout | +| `openfn project deploy --dry-run` | Prueba un despliegue, pero omite el paso de subida | +| `openfn project deploy --force` | Fuerza la subida del proyecto con checkout, ignorando cualquier advertencia de divergencia | +| `openfn ` | Ejecuta un workflow del proyecto con checkout | +| `openfn project clean` | Borra la carpeta `workflows` y todo su contenido, y luego hace checkout del proyecto | diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-usage.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-usage.md new file mode 100644 index 000000000000..32c313659842 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-usage.md @@ -0,0 +1,293 @@ +--- +title: Uso básico de la CLI de OpenFn +sidebar_label: Uso básico +slug: /cli-usage +translation_source_hash: 0cf56f75d2c15049193e021867c97df6d9a9ed09 +translation_review_status: machine +--- + +Esta página muestra ejemplos de algunos de los usos más comunes de la CLI, como: + +- obtener ayuda +- ejecutar un job +- guardar el state +- ajustar el nivel de logs +- mantener el repositorio de adaptors +- ejecutar un workflow +- preparar el código de los jobs para pruebas unitarias +- cargar la documentación de un adaptor + +--- + +### Obtener ayuda {#get-help} + +```bash +openfn --help +``` + +```bash +openfn deploy --help +``` + +--- + +### Ejecutar un job {#run-a-job} + +Para ejecutar un solo job, tienes que indicar explícitamente qué adaptor usar. +Consulta los [adaptors disponibles públicamente](/adaptors). + +Si no se detecta la versión indicada, el adaptor se instala automáticamente. + +**Ejecutar un job con el adaptor http:** + +```bash +openfn path/to/job.js -a http +``` + +**Usar una versión concreta del adaptor:** + +```bash +openfn path/to/job.js -a http@2.0.0 +``` + +**Pasar la ruta de un adaptor instalado localmente:** + +```bash +openfn path/to/job.js -a http=/repo/openfn/adaptors/my-http-build +``` + +**Usar la compilación local del monorepo de adaptors:** + +```bash +openfn path/to/job.js -ma http +``` + +Tienes que indicar la ruta al monorepo en la variable de entorno +OPENFN_ADAPTORS_REPO. Por ejemplo: + +```bash +OPENFN_ADAPTORS_REPO=~/openfn/adaptors openfn job.js -ma http +``` + +Normalmente la defines en un archivo de configuración como `.profile` o +`.zshrc`. + +¡No olvides volver a compilar el adaptor antes de usarlo! + +**Ejecutar desde un step inicial concreto** + +Puedes indicar un step con su id exacto o con una parte del nombre o del id. + +```bash +openfn path/to/job.js --start cf628d9e -s path/to/input.json +``` + +Si ya guardaste en caché los resultados de este workflow, la CLI carga +automáticamente la entrada correcta desde la caché cuando omites el argumento +`-s`: + +```bash +openfn path/to/job.js --start cf628d9e +``` + +También puedes pasar `--end` para que el workflow termine antes. + +**Ejecutar un solo step** + +`--only` funciona igual que `--start` y `--end`. Puedes indicar una parte del +nombre o del id del step, y la entrada se carga automáticamente desde la caché. + +```bash +openfn path/to/job.js --only cf628d9e +``` + +--- + +### Gestionar el state de salida {#handle-output-state} + +Cuando termina el job, la CLI escribe el state resultante en el disco. De forma +predeterminada, crea un archivo `output.json` junto al archivo del job. + +**Puedes indicar rutas personalizadas para los archivos de salida y de state:** + +```bash +openfn path/to/job.js -a adaptor-name -o path/to/output.json -s path/to/state.json +``` + +**Usa `-O` para devolver la salida por stdout:** + +```bash +openfn path/to/job.js -a adaptor-name -O +``` + +**Guardar localmente los resultados de todos los steps** + +```bash +openfn path/to/workflow.json --cache-steps +``` + +Cada step escribe su salida en `./.cli-cache//.json`. +Git ignora la carpeta `.cli-cache`, y la caché se borra cuando vuelves a +ejecutar el workflow con `--cache-steps` habilitado. + +Para guardar en caché _siempre_, define la variable de entorno +`OPENFN_ALWAYS_CACHE_STEPS` como `"true"`, y pasa `--no-cache-steps` para +deshabilitarlo temporalmente. + +--- + +### Ajustar el nivel de logs {#adjust-logging-level} + +Puedes pasar `-l info` o `--log info` para obtener más información sobre lo que +ocurre durante la ejecución. Estos son los distintos niveles de logs: + +| nivel de logs | descripción | +| --------------------------------------------- | ------------------------------------------------------------ | +| `openfn path/to/job.js -a adaptor -l none` | Modo silencioso | +| `openfn path/to/job.js -a adaptor -l default` | Información general de lo que está ocurriendo | +| `openfn path/to/job.js -a adaptor -l info` | Más información sobre el runtime, la CLI y el job | +| `openfn path/to/job.js -a adaptor -l debug` | Información sobre el runtime, la CLI, el compilador y el job | + +--- + +### Mantener el repositorio de adaptors instalados automáticamente {#maintain-auto-installed-adaptors-repo} + +**Listar el contenido del repositorio:** + +```bash +openfn repo list +``` + +**Indicar la carpeta del repositorio con la variable de entorno +`OPENFN_REPO_DIR`:** + +```bash +export OPENFN_REPO_DIR=/path/to/repo +``` + +**Instalar adaptors automáticamente y comprobar si el repositorio tiene una +versión que coincida:** + +```bash +openfn path/to/job.js -a adaptor-name +``` + +**Eliminar todos los adaptors del repositorio:** + +```bash +openfn repo clean +``` + +--- + +### Ejecutar un workflow {#run-a-workflow} + +
+ Haz clic para ver la estructura JSON de un workflow de ejemplo + +```json +{ + "options": { + "start": "a" // optionally specify the start node (defaults to steps[0]) + }, + "workflow": { + "steps": [ + { + "id": "a", + "expression": "fn((state) => state)", // code or a path + "adaptor": "@openfn/language-common@1.75", // specify the adaptor to use (version optional) + "state": { + "data": {} // optionally pre-populate the data object (this will be overridden by keys in previous state) + }, + "configuration": {}, // Use this to pass credentials + "next": { + // This object defines which steps to call next + // All edges returning true will run + // If there are no next edges, the workflow will end + "b": true, + "c": { + "condition": "!state.error" // Note that this is an expression, not a function + } + } + } + ] + } +} +``` + +
+ +**Para ejecutar un workflow:** + +```bash +openfn path/to/workflow.json -o tmp/output.json +``` + +Consulta este +[tutorial](/build-for-developers/cli-walkthrough.md#7-running-workflows) +detallado sobre cómo ejecutar workflows con la CLI. + +--- + +### Preparar el código de los jobs para pruebas unitarias {#prepare-job-code-for-unit-testing} + +¿Quieres escribir pruebas unitarias para el código de tus jobs? Consulta +[Escribir pruebas unitarias para tus jobs](/documentation/jobs/unit-testing-jobs) +para ver la guía completa. + +Las pruebas unitarias solo funcionan con las funciones puras de tu código +(declaraciones de funciones de nivel superior): no funcionan con operaciones ni +con funciones de adaptors, porque estas necesitan servicios de backend +conectados. + +Para hacer pruebas unitarias de las funciones del código de tus jobs: + +1. **Compila tu proyecto** con `openfn compile`. Esto compila tus workflows y + los escribe como módulos ES normales. +2. **Importa las funciones compiladas** en tu archivo de pruebas, igual que + cualquier otro módulo JS nativo. +3. **Escribe las pruebas como siempre** para esas funciones puras. + +**Compila todos los workflows del proyecto y conserva solo las declaraciones +exportadas:** + +```bash +openfn compile --exports-only +``` + +Los archivos compilados se escriben en `dist/` como archivos `.mjs`. + +Con `--exports-only`, las operaciones se eliminan por completo y solo quedan las +funciones y variables exportadas. + +**Compila un solo workflow por nombre:** + +```bash +openfn compile my-workflow --exports-only +``` + +**Vuelve a compilar cada vez que cambie el código de un job:** + +```bash +openfn compile --exports-only --watch +``` + +Sin `--exports-only`, obtienes la salida compilada completa del step. + +### Cargar la documentación de un adaptor {#load-adaptor-documentation} + +La CLI puede mostrar la documentación de un adaptor en la terminal. Ten en +cuenta que primero tiene que descargar el adaptor al repositorio (si todavía no +está ahí), lo que puede tardar un momento. + +**Mostrar una lista de las funciones del adaptor** + +```bash +openfn docs http +``` + +**Mostrar la documentación de una función concreta** + +```bash +openfn docs http post +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-walkthrough.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-walkthrough.md new file mode 100644 index 000000000000..cadf6af2af85 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/cli-walkthrough.md @@ -0,0 +1,902 @@ +--- +title: Recorrido por la CLI +sidebar_label: Recorrido por la CLI +slug: /cli-walkthrough +translation_source_hash: a044b0a82a85cef420e88d48a916f20e81f5ddb0 +translation_review_status: machine +--- + +### 1. Primeros pasos con la CLI {#1-getting-started-with-the-cli} + +:::info Para empezar con @openfn/cli + +1. Crea una carpeta nueva para el repositorio en el que vas a trabajar con este + comando: `mkdir devchallenge && cd devchallenge` + +2. Aunque puedes guardar tus scripts de jobs en cualquier lugar, es buena + práctica guardar `state.json` y `output.json` en una carpeta `tmp`. Para + hacerlo, crea un directorio llamado `tmp` dentro de tu carpeta + `devchallenge`: `mkdir tmp` + +3. Como `state.json` y `output.json` pueden contener información de + configuración sensible y datos del proyecto, es importante no subirlos nunca + a GitHub. Para que GitHub ignore estos archivos, agrega el directorio `tmp` a + tu archivo `.gitignore`: `echo "tmp" >> .gitignore` +4. (Opcional) Usa el comando `tree` para comprobar que la estructura de + directorios es correcta. Al ejecutar `tree -a` en tu carpeta `devchallenge` + deberías ver una estructura como esta: + ```bash + devchallenge + ├── .gitignore + └── tmp + ├── state.json + └── output.json + ``` + +::: + +1. Crea un archivo de job llamado `hello.js` y escribe el siguiente código. + + ```js + console.log('Hello World!'); + ``` + +
+ ¿Qué es un job? + Un job de OpenFn es código JavaScript que sigue un conjunto concreto de convenciones. + Normalmente un job tiene una o más operaciones que realizan una tarea concreta + (como obtener información de una base de datos, crear un registro, etc.) y + devuelven el state para que lo use la siguiente operación. +
+ +
+ ¿Qué es console.log? + console.log es una función básica del lenguaje JavaScript que te + permite mostrar mensajes en la ventana de la terminal. +
+ +2. Ejecuta el job con la CLI + + ```bash + openfn hello.js -o tmp/output.json + ``` + +
+ Ver la salida esperada + + ```bash + [CLI] ⚠ WARNING: No adaptor provided! + [CLI] ⚠ This job will probably fail. Pass an adaptor with the -a flag, eg: + openfn job.js -a common + [CLI] ✔ Compiled from hello.js + [R/T] ♦ Starting job job-1 + [JOB] ℹ Hello World! + [R/T] ✔ Completed job job-1 in 1ms + [CLI] ✔ State written to tmp/output.json + [CLI] ✔ Finished in 17ms ✨ + + ``` + +
+ +Fíjate en que tu instrucción `console.log` se imprimió como +`[JOB] Hello World!`. Usar la consola así ayuda a depurar o a entender qué pasa +dentro de tus steps. + +### 2. Usar las funciones auxiliares de los adaptors {#2-using-adaptor-helper-functions} + +Los adaptors son módulos de JavaScript o +[TypeScript](https://www.typescriptlang.org/) (un superconjunto de JavaScript +con tipado fuerte) que ofrecen a los usuarios de OpenFn un conjunto de funciones +auxiliares para simplificar la comunicación con un sistema externo concreto. Más +información sobre los adaptors: [docs.openfn.org/adaptors](/adaptors/) + +#### Uso básico: {#basic-usage} + +Usemos el adaptor +[@openfn/language-http](https://www.npmjs.com/package/@openfn/language-http) +para obtener una lista de formularios de +[https://jsonplaceholder.typicode.com/](https://jsonplaceholder.typicode.com/) + +#### Tareas: {#tasks} + +1. Crea un archivo llamado `getPosts.js` y escribe el siguiente código + + ```jsx title=getPosts.js + get('https://jsonplaceholder.typicode.com/posts'); + fn(state => { + console.log(state.data[0]); + return state; + }); + ``` + +2. Ejecuta el job con este comando + +```bash +openfn getPosts.js -i -a http -o tmp/output.json +``` + +:::info Los argumentos de la CLI + +Usa `-a` para indicar el adaptor y `-i` para instalar automáticamente el adaptor +necesario + +Ejecuta `openfn help` para ver la lista completa de argumentos de la CLI. + +::: + +Como es la primera vez que usas el adaptor `http`, lo instalas con el argumento +`-i`. + +
+ 3. Expande para ver los logs esperados de la CLI + +```bash + [CLI] ✔ Installing packages... + [CLI] ✔ Installed @openfn/language-http@4.2.8 + [CLI] ✔ Installation complete in 14.555s + [CLI] ✔ Compiled from getPosts.js + [R/T] ♦ Starting job job-1 + GET request succeeded with 200 ✓ + [JOB] ℹ { + userId: 1, + id: 1, + title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', + body: 'quia et suscipit\n' + + 'suscipit recusandae consequuntur expedita et cum\n' + + 'reprehenderit molestiae ut ut quas totam\n' + + 'nostrum rerum est autem sunt rem eveniet architecto' + } + [R/T] ✔ Completed job job-1 in 872ms + [CLI] ✔ State written to tmp/output.json + [CLI] ✔ Finished in 15.518s ✨ + +``` + +
+ +:::warning Datos de ejemplo + +Los datos que aparecen en estos logs de la CLI provienen de la API de +[JSONPlaceholder](https://jsonplaceholder.typicode.com/) y no representan +información real. Solo sirven para pruebas y desarrollo. + +Para hacer pruebas precisas, considera usar datos reales de tu API o servicio. + +::: + +### 3. Entender `state` {#3-understanding-state} + +Si la expresión de un job es un conjunto de instrucciones para un chef (¿una +receta?), el state inicial son todos los ingredientes que necesita, bien atados +en un paquetito perfecto. Consulta +[It all starts with state](/articles/2021/07/05/wrapping-my-head-around-jobs/#it-all-starts-with-state) +en la base de conocimiento para más contexto. + +
+ Suele verse más o menos así + +```json +{ + "configuration": { + "hostUrl": "https://moh.kenya.gov.ke/dhis2", + "username": "someone", + "password": "something-secret" + }, + "data": { + "type": "registration", + "patient": { + "age": 24, + "gender": "M", + "nationalId": "321cs7" + } + } +} +``` + +
+ +#### `state.configuration` {#stateconfiguration} + +En esta clave van las credenciales que autorizan las conexiones con cualquier +sistema autenticado con el que interactúe el job. (Ten en cuenta que, cuando +usas la plataforma OpenFn en lugar de la CLI, esta parte de `state` suele +sobrescribirse en tiempo de ejecución con una "credencial" real). + +:::warning Importante + +Ten en cuenta que `console.log(state)` muestra todo el state, incluidos +elementos de `state.configuration` como el **nombre de usuario y la +contraseña**. Elimina este log cuando termines de depurar, para no exponer +información sensible por accidente cuando el job se despliegue en producción. + +La plataforma OpenFn tiene protecciones integradas para "limpiar" el state de +los logs, pero cuando usas la CLI directamente, ¡estás por tu cuenta! + +::: + +#### `state.data` {#statedata} + +En esta clave van los datos relacionados con un run concreto de un job. En la +plataforma, son los datos propios de la work order que vienen de una solicitud +HTTP que activa el trigger, o algún dato que se pasa de un job a otro. + +Con la CLI, `state.json` se carga automáticamente desde el directorio actual. + +También puedes indicar la ruta del archivo de state con la opción -s, +--state-path. + +Indica la ruta de tu archivo `state.json` con este comando: + +```bash +openfn hello.js -a http -s tmp/state.json -o tmp/output.json +``` + +
+ Expande para ver los logs esperados de la CLI + +``` +[CLI] ✔ Compiled job from hello.js +GET request succeeded with 200 ✓ +[R/T] ✔ Operation 1 complete in 876ms +[R/T] ✔ Operation 2 complete in 0ms +[CLI] ✔ Writing output to tmp/output.json +[CLI] ✔ Done in 1.222s! ✨ +``` + +
+ +#### ¿Cómo puedes usar el state? {#how-can-we-use-state} + +Cada adaptor tiene un esquema de configuración recomendado para tu `state.json`. +El +[esquema de configuración de http](/adaptors/packages/http-configuration-schema) +muestra cómo configurar `state.configuration` para `language-http`: + +```json +{ + "username": "name@email", + "password": "supersecret", + "baseUrl": "https://jsonplaceholder.typicode.com" +} +``` + +#### Tareas: {#tasks-1} + +1. Actualiza tu `state.json` para que quede así: + +
+ Expande para ver state.json + + ```json title=state.json + { + "configuration": { + "baseUrl": "https://jsonplaceholder.typicode.com" + } + } + ``` + +
+ +Como actualizaste la configuración en tu `state.json`, ahora puedes usar la +función auxiliar `get()` sin indicar la **baseUrl**, es decir, `get('posts')`. + +2. Actualiza tu job `getPosts.js` para que quede así: + +
+ Expande para ver getPosts.js + + ```js title="getPosts.js" + // Get all posts + get('posts'); + + fn(state => { + const posts = state.data; + console.log(posts[0]); + return state; + }); + ``` + +
+ +3. Ahora ejecuta el job con el siguiente comando + + ```bash + openfn getPosts.js -a http -s tmp/state.json -o tmp/output.json + ``` + +
+ Y comprueba que ves los logs esperados de la CLI: + + ```bash + [CLI] ✔ Compiled job from getPosts.js + GET request succeeded with 200 ✓ + [R/T] ✔ Operation 1 complete in 120ms + [JOB] ℹ { + userId: 1, + id: 1, + title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', + body: 'quia et suscipit\n' + + 'suscipit recusandae consequuntur expedita et cum\n' + + 'reprehenderit molestiae ut ut quas totam\n' + + 'nostrum rerum est autem sunt rem eveniet architecto' + } + [R/T] ✔ Operation 2 complete in 0ms + [CLI] ✔ Writing output to tmp/output.json + [CLI] ✔ Done in 470ms! ✨ + + ``` + +
+ +### 4. Limpiar y transformar datos {#4-clean--transform-data} + +En la mayoría de los casos necesitas manipular, limpiar o transformar datos en +algún step de tu workflow. Por ejemplo, después de obtener datos del registro +`https://jsonplaceholder.typicode.com`, quizás necesites agrupar las +publicaciones por id de usuario. El ejemplo de abajo muestra cómo: + +1. obtener todas las publicaciones y devolverlas en `state.data` +2. agrupar las publicaciones devueltas por `userId` +3. mostrar en el log las publicaciones con userId `1` + +
+Expande para ver el ejemplo: + +```js title="getPosts.js" +// Get all posts +get('posts'); + +// Group posts by user id +fn(state => { + const posts = state.data; + + // Group posts by userId + const groupPostsByUserId = posts.reduce((acc, post) => { + const existingValue = acc[post.userId] || []; + return { + ...acc, + [post.userId]: [...existingValue, post], + }; + }, {}); + + console.log(groupPostsByUserId); + + return { ...state, groupPostsByUserId }; +}); + +// Log posts where userId = 1 +fn(state => { + const { groupPostsByUserId } = state; + console.log('Post with userId 1', groupPostsByUserId[1]); + return state; +}); +``` + +
+ +
+¿Qué es array.reduce? +El método reduce() aplica una función a un acumulador y a cada +valor del array (de izquierda a derecha) para reducirlo a un único valor. + +Quizás el caso más fácil de entender de reduce() es devolver la +suma de todos los elementos de un array: + +##### Demostración de JavaScript: `Array.reduce()` {#javascript-demo-arrayreduce} + +``` + +// 0 + 1 + 2 + 3 + 4 +const array1 = [1, 2, 3, 4]; +const initialValue = 0; +const sumWithInitial = array1.reduce( + (accumulator, currentValue) => accumulator + currentValue, + initialValue +); + +console.log(sumWithInitial); // Expected output: 10 + +``` + +Puedes aprender más sobre `array.reduce` en la +[referencia de MDN de Array.prototype.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce). + +
+ +
+ Expande para ver los logs esperados de la CLI + +``` + +[CLI] ✔ Compiled job from getPosts.js +GET request succeeded with 200 ✓ +[R/T] ✔ Operation 1 complete in 825ms +[R/T] ✔ Operation 2 complete in 0ms +[JOB] ℹ Post with userId 1 [ //All of posts for userId 1 ] +[R/T] ✔ Operation 3 complete in 12ms +[CLI] ✔ Writing output to tmp/output.json +[CLI] ✔ Done in 1.239s! ✨ + +``` + +
+ +### 5. Depurar errores {#5-debugging-errors} + +Al depurar, es interesante y útil usar console.log para ver el contenido de los +objetos que manipulas (como state). + +Cuando quieras inspeccionar el contenido de state entre operaciones, agrega un +bloque `fn()` con un `console.log`: + +```js +// firstOperation(...); + +fn(state => { + console.log(state); + return state; +}); + +// secondOperation(...); +``` + +##### Crea **debug.js** y pega el código de abajo {#create-debugjs-and-paste-the-code-below} + +
+ + Expande para ver debug.js + +```jsx title="debug.js" +// Get all posts +get('posts'); + +// Get post by index helper function +fn(state => { + // const getPostbyIndex = (index) => dataValue(index)(state); + console.log(dataValue(1)); + + return { ...state }; +}); +``` + +
+ +##### Ejecuta **openfn debug.js -a http -s tmp/state.json** {#run-openfn-debugjs--a-http--s-tmpstatejson} + +
+ Logs esperados de la CLI + +```bash +[CLI] ✘ TypeError: path.match is not a function + at dataPath (/tmp/openfn/repo/node_modules/@openfn/language-common/dist/index.cjs:258:26) + at dataValue (/tmp/openfn/repo/node_modules/@openfn/language-common/dist/index.cjs:262:22) + at getPostbyIndex (vm:module(0):5:37) + at vm:module(0):18:36 + at /tmp/openfn/repo/node_modules/@openfn/language-common/dist/index.cjs:241:12 + at file:///home/openfn/.asdf/installs/nodejs/18.12.0/lib/node_modules/@openfn/cli/node_modules/@openfn/runtime/dist/index.js:288:26 + at process.processTicksAndRejections (node:internal/process/task_queues:95:5) + at async run (file:///home/openfn/.asdf/installs/nodejs/18.12.0/lib/node_modules/@openfn/cli/node_modules/@openfn/runtime/dist/index.js:269:18) + at async executeHandler (file:///home/openfn/.asdf/installs/nodejs/18.12.0/lib/node_modules/@openfn/cli/dist/process/runner.js:388:20) +``` + +
+ +Como ves en los logs, la función auxiliar `dataValue` tiene un TypeError. Para +solucionarlo, puedes ir a la documentación de **dataValue -> +[docs.openfn.org/adaptors/packages/common-docs/#datavalue](/adaptors/packages/common-docs/#datavalue)**. + +Según la documentación, dataValue recibe como entrada una ruta de tipo string. +Pero en tu operación pasabas un entero; por eso aparece el _TypeError_. Puedes +corregir el error pasando un string a dataValue, es decir, +`console.log(dataValue("1"))`. + +
+ Logs esperados de la CLI + +```bash +[CLI] ✔ Compiled job from debug.js +GET request succeeded with 200 ✓ +[R/T] ✔ Operation 1 complete in 722ms +[JOB] ℹ [Function (anonymous)] +[R/T] ✔ Operation 2 complete in 1ms +[CLI] ✔ Writing output to tmp/output.json +[CLI] ✔ Done in 1.102s ✨ +``` + +
+ +Si necesitas más información para depurar, puedes pasar `-l debug`. Esto +establece el nivel de log en _debug_, que registra toda la información de la +ejecución. + +Es decir, `openfn debug.js -a http -l debug`. + +### 6. Each e iteración de arrays {#6-each-and-array-iteration} + +A menudo tienes que realizar la misma operación varias veces, una por cada +elemento de un array. La mayoría de las funciones auxiliares para manipular +datos se heredan de @openfn/language-common y están disponibles en la mayoría de +los adaptors. + +##### Modifica getPosts.js para agrupar las publicaciones por ID de usuario {#modify-getpostsjs-to-group-posts-by-user-id} + +
+Expande para ver getPosts.js + +```js title="getPosts.js" +// Get all posts +get('posts'); + +// Group posts by user +fn(state => { + const posts = state.data; + + // Group posts by userId + const groupPostsByUserId = posts.reduce((acc, post) => { + const existingValue = acc[post.userId] || []; + return { ...acc, [post.userId]: [...existingValue, post] }; + }, {}); + + // console.log(groupPostsByUserId); + return { ...state, groupPostsByUserId }; +}); + +// Log posts where userId = 1 +fn(state => { + const { groupPostsByUserId } = state; + const posts = groupPostsByUserId[1]; + + // console.log("Post with userId 1", groupPostsByUserId[1]); + return { ...state, posts }; +}); + +each('posts[*]', state => { + console.log('Post', JSON.stringify(state.data, null, 2)); + return state; +}); +``` + +
+ +Fíjate en que este código usa la función `each`, una función auxiliar definida +en [language-common](/adaptors/packages/common-docs/#each) pero a la que se +accede desde este job, que usa `language-http`. La mayoría de los adaptors +importan muchas funciones de `language-common`. + +Ejecuta **openfn getPosts.js -a http -s tmp/state.json -o tmp/output.json** + +
+ Expande para ver los logs esperados de la CLI + +```bash +[CLI] ✔ Compiled job from getPosts.js +GET request succeeded with 200 ✓ +[R/T] ✔ Operation 1 complete in 730ms +[R/T] ✔ Operation 2 complete in 0ms +[R/T] ✔ Operation 3 complete in 0ms +[JOB] ℹ Posts [ +// Posts +] +[R/T] ✔ Operation 4 complete in 10ms +[CLI] ✔ Writing output to tmp/output.json +[CLI] ✔ Done in 1.091s! ✨ +``` + +
+ +### 7. Ejecutar workflows {#7-running-workflows} + +Ejecutar un workflow te permite definir una lista de steps y las reglas para +ejecutarlos. Puedes usar un workflow para orquestar el flujo de datos entre +sistemas de forma estructurada y automatizada. + +Por ejemplo, si tu workflow tiene dos steps (GET de usuarios del sistema A y +POST de usuarios al sistema B), puedes configurarlo para que ejecute todos los +steps en secuencia, de principio a fin. Esto imita los +[patrones de flow triggers](/documentation/legacy/build/triggers#flow-triggers) +de la plataforma OpenFn, donde un segundo job debería ejecutarse después de que +el primero termine con éxito, usando los datos que devolvió el primer job. + +:::info En resumen + +No tendrás que armar el state inicial del siguiente job: el state final del job +anterior se pasa automáticamente al job siguiente como state inicial. + +::: + +##### Workflow {#workflow} + +Un workflow es el plan de ejecución de varios steps en secuencia. Se define como +un objeto JSON con las siguientes propiedades: + +```json +{ + "options": { + "start": "a" // optionally specify the start node (defaults to steps[0]) + }, + "workflow": { + "steps": [ + { + "id": "a", + "expression": "fn((state) => state)", // code or a path + "adaptor": "@openfn/language-common@1.75", // specify the adaptor to use (version optional) + "state": { + "data": {} // optionally pre-populate the data object (this will be overridden by keys in previous state) + }, + "configuration": {}, // Use this to pass credentials + "next": { + // This object defines which steps to call next + // All edges returning true will run + // If there are no next edges, the workflow will end + "b": true, + "c": { + "condition": "!state.error" // Note that this is an expression, not a function + } + } + } + ] + } +} +``` + +###### Ejemplo de un workflow {#example-of-a-workflow} + +
+Este es un ejemplo de un workflow sencillo con tres steps: + +```json title="workflow.json" +{ + "options": { + "start": "getPatients" + }, + "workflow": { + "steps": [ + { + "id": "getPatients", + "adaptor": "http", + "expression": "getPatients.js", + "configuration": "tmp/http-creds.json", + "next": { + "getGlobalOrgUnits": true + } + }, + { + "id": "getGlobalOrgUnits", + "adaptor": "common", + "expression": "getGlobalOrgUnits.js", + "next": { + "createTEIs": true + } + }, + { + "id": "createTEIs", + "adaptor": "dhis2", + "expression": "createTEIs.js", + "configuration": "tmp/dhis2-creds.json" + } + ] + } +} +``` + +
+ +
+ tmp/http-creds.json + +```json title="tmp/http-creds.json" +{ + "baseUrl": "https://jsonplaceholder.typicode.com" +} +``` + +
+ +
+ tmp/dhis2-creds.json + +```json title="tmp/dhis2-creds.json" +{ + "hostUrl": "https://play.im.dhis2.org/dev", + "password": "district", + "username": "admin" +} +``` + +
+ +
+ getPatients.js + +```js title="getPatients.js" +// Get users from jsonplaceholder +get('users'); + +// Prepare new users as new patients +fn(state => { + const newPatients = state.data; + return { ...state, newPatients }; +}); +``` + +
+ +
+ getGlobalOrgUnits.js + +```js title="getGlobalOrgUnits.js" +// Globals: orgUnits +fn(state => { + const globalOrgUnits = [ + { + label: 'Njandama MCHP', + id: 'g8upMTyEZGZ', + source: 'Gwenborough', + }, + { + label: 'Njandama MCHP', + id: 'g8upMTyEZGZ', + source: 'Wisokyburgh', + }, + { + label: 'Njandama MCHP', + id: 'g8upMTyEZGZ', + source: 'McKenziehaven', + }, + { + label: 'Njandama MCHP', + id: 'g8upMTyEZGZ', + source: 'South Elvis', + }, + { + label: 'Ngelehun CHC', + id: 'IpHINAT79UW', + source: 'Roscoeview', + }, + { + label: 'Ngelehun CHC', + id: 'IpHINAT79UW', + source: 'South Christy', + }, + { + label: 'Ngelehun CHC', + id: 'IpHINAT79UW', + source: 'Howemouth', + }, + { + label: 'Ngelehun CHC', + id: 'IpHINAT79UW', + source: 'Aliyaview', + }, + { + label: 'Baoma Station CHP', + id: 'jNb63DIHuwU', + source: 'Bartholomebury', + }, + { + label: 'Baoma Station CHP', + id: 'jNb63DIHuwU', + source: 'Lebsackbury', + }, + ]; + + return { ...state, globalOrgUnits }; +}); +``` + +
+ +
+ createTEIs.js + +```js title="createTEIs.js" +fn(state => { + const { newPatients, globalOrgUnits } = state; + + const getOrgUnit = city => + globalOrgUnits.find(orgUnit => orgUnit.source === city).id; + + const mappedEntities = newPatients.map(patient => { + const [firstName = 'Patient', lastName = 'Test'] = ( + patient.name || '' + ).split(' '); + + const orgUnit = getOrgUnit(patient.address.city); + + const attributes = [ + { attribute: 'w75KJ2mc4zz', value: firstName }, + { attribute: 'zDhUuAYrxNC', value: lastName }, + { attribute: 'cejWyOfXge6', value: 'Male' }, + ]; + + return { ...patient, attributes: attributes, orgUnit: orgUnit }; + }); + + return { ...state, mappedEntities }; +}); + +each( + 'mappedEntities[*]', + create('trackedEntityInstances', { + orgUnit: dataValue('orgUnit'), + trackedEntityType: 'nEenWmSyUEp', + attributes: dataValue('attributes'), + }) +); +``` + +
+ +Para ejecutar el workflow, usa `openfn [path/to/workflow.json]`. + +
+ +Por ejemplo, si creaste workflow.json en la raíz del directorio de +tu proyecto, esta sería la estructura del proyecto: + + +```bash + devchallenge + ├── .gitignore + ├── getPatients.js + ├── createTEIs.js + ├── getGlobalOrgUnits.js + ├── workflow.json + └── tmp + ├── http-creds.json + ├── dhis2-creds.json + └── output.json +``` + +
+ +```bash +openfn workflow.json -o tmp/output.json +``` + +Al ejecutarse, este workflow empieza por el job `getPatients.js`. Si termina con +éxito, `getGlobalOrgUnits.js` se ejecuta con el state final de `getPatients.js`. +Si `getGlobalOrgUnits.js` termina con éxito, `createTEIs.js` se ejecuta con el +state final de `getGlobalOrgUnits.js`. + +Ten en cuenta que los adaptors indicados en `workflow.json` se instalan +automáticamente cuando ejecutas el workflow. Para ejecutar el workflow, usa este +comando: + +```bash +openfn workflow.json -o tmp/output.json +``` + +Al ejecutarlo, primero se instalan automáticamente los adaptors y luego se +ejecuta el workflow. + +:::danger Importante + +Cuando trabajes con el archivo `workflow.json`, es importante manejar de forma +segura la información sensible, como las credenciales y los datos de entrada +iniciales. Para proteger tus datos sensibles, sigue estas pautas: + +1. Clave de configuración: en el archivo `workflow.json`, indica la ruta a un + archivo de configuración ignorado por git que contenga las credenciales + necesarias para acceder al sistema de destino. Por ejemplo: + + ```json + { + ... + "configuration": "tmp/openMRS-credentials.json" + }, + ``` + +2. Clave de datos: si necesitas pasar datos iniciales a tu job, indica la ruta a + un archivo de datos ignorado por git: + ```json + { + ... + "state": { + "data": "tmp/initial-data.json", + } + } + ``` + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/security-for-devs.md b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/security-for-devs.md new file mode 100644 index 000000000000..75e190f2b60d --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build-for-developers/security-for-devs.md @@ -0,0 +1,198 @@ +--- +id: security-for-devs +title: Consideraciones de seguridad para el desarrollo con OpenFn +sidebar_label: Consideraciones de seguridad +slug: /security-for-devs +translation_source_hash: 82ba4f8c0b4cee7db4d2c18b5d0235b7d72436f6 +translation_review_status: machine +--- + +## Introducción {#introduction} + +Aunque las aplicaciones que integras sean seguras, las implementaciones de +integración de datos siguen teniendo muchos riesgos de seguridad. Sigue leyendo +para conocer buenas prácticas y consejos que te ayudarán a lograr la máxima +seguridad al crear workflows de OpenFn. + +## Errores comunes y cómo evitarlos {#common-mistakes-and-how-to-avoid-them} + +Escribir jobs de OpenFn puede ser sencillo, pero hay errores comunes que los +desarrolladores pueden cometer. Estos son algunos de ellos y consejos para +evitarlos: + +### Información sensible escrita en el código {#hardcoding-sensitive-information} + +- **Evita escribir credenciales en el código:** en lugar de escribir nombres de + usuario, contraseñas o claves de API directamente en tus scripts, usa el + objeto `state` para guardar y obtener la información sensible. + +```json +// Store credentials in state.json +{ + "configuration": { + "username": "your_username", + "password": "your_password" + } +} +``` + +```javascript +// Retrieve credentials from state +const username = state.configuration.username; +const password = state.configuration.password; +``` + +### Subir datos sensibles a GitHub {#checking-sensitive-data-into-github} + +- **Cuida el contenido del repositorio:** cuando trabajes en proyectos de + clientes, no guardes datos sensibles, como `state.json`, en el repositorio + público de GitHub del cliente. Revisa siempre el contenido del repositorio y + elimina u oculta la información sensible antes de hacer commit de tus cambios. + +- **Usa directorios específicos para los payloads de prueba:** si guardas + payloads de prueba en el repositorio de un cliente, usa directorios como + `sampleData/` y asegúrate de que los nombres de los archivos no se parezcan a + `state.json`. No incluyas objetos state completos en estos archivos; en su + lugar, reproduce exactamente el payload de prueba. + +- **Revisa el git diff antes de hacer commit:** antes de hacer un commit, lee + con atención el git diff línea por línea para detectar cambios y logs que no + querías incluir. Acostúmbrate a hacer commits selectivos + (`git add ./path/to/file`) en lugar de usar `git add -A`. + +- **Crea el hábito de revisar los diffs:** para crear el hábito, lee el primer + archivo en `git diff` y luego usa `git add ./that_file` si está listo. + Continúa revisando el diff y agregando archivos según sea necesario. Por + último, usa `git commit -m "my changes"` y haz push. + +### Uso de `console.log()` {#use-of-consolelog} + +- **Registra los logs con cuidado:** aunque `console.log()` es fundamental + durante el desarrollo de un job, es esencial tener cuidado de no registrar + información sensible. Una vez creados los jobs, los administradores pueden + desactivar la salida de los logs de consola, pero algunos aspectos del sandbox + podrían seguir mostrándose. + +- **Evita registrar datos sensibles:** ten cuidado al usar `console.log(state)` + o `console.log(state.configuration)`, sobre todo si contienen información + sensible como nombres de usuario y contraseñas. En algunos casos, esto puede + exponer datos sensibles en los logs. + +```javascript +// Good practice +console.log('Operation completed successfully.'); + +// Avoid logging sensitive data +console.log('Received sensitive data:', state.data); +``` + +- **Logs pensados para auditorías:** ten en cuenta que algunos clientes usan + `console.log()` con fines de auditoría, incluso en producción. Asegúrate de + que tu forma de registrar logs se ajuste a los requisitos del cliente y evita + exponer información confidencial sin querer. + +- **Control de los administradores:** ten en cuenta que los administradores + pueden controlar la visibilidad de los logs de consola en un proyecto de + OpenFn. Aun así, es fundamental registrar los logs de una forma que responda + tanto a las necesidades del desarrollo como a las consideraciones de seguridad + del cliente. + +> Estas buenas prácticas en el uso de `console.log()` contribuyen a un +> desarrollo seguro de jobs en OpenFn y reducen el riesgo de exponer datos +> sensibles en los logs sin querer. + +### No manejar los errores {#ignoring-error-handling} + +- **Maneja los errores de forma controlada:** implementa siempre el manejo de + errores en tus scripts. Incluye mensajes de error útiles en el objeto `state` + para facilitar la depuración sin exponer detalles sensibles en las respuestas. + +```javascript +// Set custom error message in state +state.error = new Error( + 'Authentication failed. Please check your credentials.' +); + +// Log the error in OpenFn +console.error(state.error); +``` + +### Permisos demasiado amplios en las credenciales {#overly-broad-permissions-on-credentials} + +Al agregar credenciales a OpenFn, es imprescindible seguir el principio de +mínimo privilegio. En concreto: + +- **Cuentas de usuario especializadas:** + + - **Usuario específico para el cliente:** crea una cuenta de usuario dedicada + para el sistema del cliente dentro de la aplicación de terceros (por + ejemplo, Salesforce). Esta cuenta de usuario debería usarse solo para la + integración con OpenFn. + + - **Acceso limitado a la API:** concede acceso a la API solo a los recursos + necesarios para las operaciones de OpenFn. Asegúrate de que los permisos se + limiten a los objetos y campos específicos que requiere el intercambio de + datos. + +- **Ámbitos de OAuth:** + + - **Control de ámbitos con OAuth:** en algunos casos, se usa OAuth para una + autenticación segura. Aprovecha las capacidades de OAuth para controlar los + ámbitos de acceso. Define y limita los permisos según los requisitos exactos + de OpenFn. + +- **Ejemplo de Salesforce:** + + - **Restricciones de objetos y campos:** en Salesforce, en concreto, crea una + cuenta de usuario con acceso a la API limitado a los objetos y campos + pertinentes. No concedas permisos innecesarios que podrían suponer riesgos + de seguridad. + + > Con estas prácticas, te aseguras de que las credenciales asociadas a + > OpenFn tengan permisos definidos y restringidos con precisión. Así se + > reduce el riesgo de accesos no deseados y se protegen los datos sensibles + > del cliente durante los procesos de integración. + +### Conservar tu proyecto de OpenFn {#retaining-your-openfn-project} + +Si tienes requisitos estrictos de residencia de datos, puedes configurar OpenFn +como un pipeline de datos de "retención cero" para garantizar el cumplimiento. +Así, no se conserva ningún dato procesado en los workflows de OpenFn (entradas y +salidas), ni siquiera en la oferta de la plataforma de OpenFn alojada en la +nube. + +A muchos usuarios les resulta útil conservar los datos en OpenFn de forma +temporal para resolver problemas. Por ejemplo, si tienes un workflow de 3 steps +y el workflow falla en el step 3, podría ser útil conservar la entrada de ese +step fallido para inspeccionar los datos, resolver el problema rápidamente y +volver a intentarlo desde ese punto. Para tener esta experiencia de resolución +de problemas más sencilla, la mayoría de los usuarios deja activada la retención +temporal de datos, y el superadministrador de OpenFn puede ajustar el periodo de +retención. + +### Prácticas de seguridad para adaptors {#adaptors-security-practices} + +- **Protección del state del cliente:** los adaptors (paquetes de lenguaje) + nunca deberían exponer directamente ninguna parte del state de un cliente. Los + callbacks pueden registrar partes del state, pero deberían evitar registrar la + configuración o los datos reales, ya que pueden contener información de + identificación personal (PII). + +- **Uso de metadatos:** los adaptors pueden basarse en metadatos sobre el state, + como registrar el "número de casos obtenidos" en una solicitud. Sin embargo, + está prohibido registrar la solicitud en sí. + +- **Proceso de revisión de seguridad:** a medida que más colaboradores + participan en los adaptors, las revisiones de seguridad rigurosas de las pull + requests pasan a ser fundamentales. Asegúrate de que se cumplan las prácticas + de seguridad descritas durante el proceso de revisión. + +:::tip Más información + +Para conocer más consideraciones de seguridad y buenas prácticas para todas las +personas que implementan OpenFn (no solo los desarrolladores), consulta la +[Guía de seguridad de OpenFn](/get-started/security.md) completa. Para saber más +sobre cómo escribir jobs, consulta la +[guía para escribir jobs](/jobs/job-writing-guide.md). + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/ai-assistant.md b/i18n/es/docusaurus-plugin-content-docs/current/build/ai-assistant.md new file mode 100644 index 000000000000..8b60540dc2bc --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/ai-assistant.md @@ -0,0 +1,165 @@ +--- +title: Usar el AI Assistant +sidebar_label: AI Assistant +translation_source_hash: 8f3412d45ba140eef764450fc214945e39981ba4 +translation_review_status: machine +--- + +El AI Assistant de OpenFn ofrece una interfaz de chat con un modelo de IA que te +ayuda a crear workflows. **Mira +[este video](https://www.youtube.com/watch?v=3L_cGl9tWRc&ab_channel=OpenFn.org) +para ver una introducción a cómo funciona.** + +Puedes usarlo para escribir borradores de código de jobs, revisarlo y depurarlo, +diagnosticar errores y entender lo que puede hacer la plataforma. + +:::info Crea workflows basados en IA en OpenFn + +Consulta los [adaptors](/adaptors) de OpenFn para crear workflows de OpenFn que +orquesten interacciones con LLM (como ChatGPT y Claude) y conviertan las +decisiones tomadas con IA en acciones y ejecución automatizada. + +::: + +

+ +

+ +:::caution ¿El Assistant no está disponible? ¿No lo encuentras? + +En las instalaciones locales de OpenFn, el administrador de la instancia tiene +que configurar el AI Assistant para que esté disponible. Consulta la +[documentación de despliegue](https://github.com/OpenFn/lightning/blob/main/DEPLOYMENT.md#ai-chat) +para obtener ayuda o contacta al superusuario de tu instancia. + +El Assistant está disponible en app.openfn.org, con créditos de uso según el +plan de tu proyecto. Consulta +[openfn.org/pricing](https://www.openfn.org/pricing) o contacta a +[support@openfn.org](mailto:support@openfn.org) para saber más sobre los planes +de pago de la plataforma de OpenFn alojada en la nube. + +::: + +## Sobre el Assistant {#about-the-assistant} + +El AI Assistant es un sistema multiagente personalizado. Tiene acceso a la +documentación de OpenFn y a buenas prácticas de implementación, así que puede +responder tus preguntas en el contexto de la plataforma. + +Todas las sesiones de chat se comparten entre todos los usuarios del proyecto. +Puedes empezar una sesión de chat nueva cuando quieras, o abrir una anterior. + +Puedes configurar si se envían al modelo el código de tu workflow, los logs de +los runs y los datos de entrada y salida. Compartir este contexto permite que el +Assistant dé una respuesta más pertinente, pero piensa bien si los datos son +confidenciales o sensibles antes de enviarlos. + +## Una nota sobre el uso responsable de la IA {#a-note-on-responsible-ai-usage} + +El AI Assistant usa modelos de lenguaje de gran tamaño (LLM). Como otros +chatbots, sus capacidades son impresionantes, pero imperfectas. + +Recuerda que, en definitiva, todas las respuestas se generan automáticamente y +TÚ, la persona a cargo, eres responsable de cómo se usa lo que produce. Deberías +analizar todas las respuestas con espíritu crítico y verificar el resultado +siempre que puedas. + +**Puedes leer más sobre nuestro enfoque de la IA en nuestra +[Política de IA responsable](https://www.openfn.org/ai).** + +## Cómo acceder al AI Assistant {#how-to-access-the-ai-assistant} + +Puedes acceder al AI Assistant desde el Canvas del workflow o desde un step de +job concreto, haciendo clic en el ícono de globo de diálogo de la esquina +superior derecha. + +Si ya hubo sesiones de chat anteriores, verás una lista con ellas. Haz clic en +una para abrir el historial de ese chat. + +Para empezar una sesión nueva, escribe una pregunta en el área de texto de la +parte inferior del Assistant. Haz clic en el botón `Send` para enviar tu +pregunta. El Assistant te devolverá una respuesta en la interfaz de chat. + +Puedes cerrar una sesión de chat haciendo clic en el botón `(X)` de la esquina +superior derecha de la interfaz de chat, que te lleva de vuelta a la lista de +sesiones. + +## Limpieza de datos {#data-scrubbing} + +Si decides enviar al Assistant los datos de entrada y salida de tus runs, el +Assistant recibe la forma de tus datos, no los datos en sí. Cada valor se +reemplaza por el tipo de dato que era. Los nombres de los campos se mantienen, +porque el Assistant los necesita para hablar de tus datos de forma útil. Ten en +cuenta que los logs de los runs y el código no se limpian. + +Así que, si un step se ejecutó con esto: + +```json +{ + "patient": { + "name": "Amina Yusuf", + "dob": "2000-01-01", + "phone": "+123456789", + "visits": 3, + "consented": true, + "notes": null + }, + "records": [ + { "id": "R-001", "weight": 61.5 }, + { "id": "R-002", "weight": 58.0 }, + { "id": "R-003", "weight": 70.2 }, + { "id": "R-004", "weight": 64.1 } + ] +} +``` + +esto es lo que recibe el Assistant: + +```json +{ + "patient": { + "consented": "boolean", + "dob": "string", + "name": "string", + "notes": "null", + "phone": "string", + "visits": "number" + }, + "records": [ + { "id": "string", "weight": "number" }, + { "id": "string", "weight": "number" }, + "...2 more" + ] +} +``` + +El nombre, la fecha de nacimiento y el número de teléfono nunca salen de OpenFn. +El Assistant puede ver igualmente que hay un paciente con un número de teléfono +y cuatro registros, que suele ser todo lo que necesita para ayudarte a corregir +el código de tu job. + +### Otras cosas que hace {#a-few-other-things-it-does} + +- Las listas largas se recortan a dos ejemplos. El resto se cuenta, como + `"...2 more"` en el ejemplo de arriba. +- Los registros muy anchos se recortan a 50 campos. El resto se cuenta en un + campo `"..."`. +- Si la política de retención de tu proyecto ya borró los datos de un step, al + Assistant se le envía `[erased by this project's retention policy]` en su + lugar. Se le avisa cuando se omitieron datos, para que no dé por hecho que lo + vio todo. +- Los datos muy grandes no se envían. Verás `[too large to summarise]`. + +### Lo único que tienes que saber {#the-one-thing-to-know} + +Los nombres de los campos se envían tal cual. Si tus datos usan el nombre de una +persona o un número de documento nacional de identidad como nombre de campo, ese +nombre o número se enviará. Los valores están protegidos; las claves, no. + +:::caution ¿Comentarios o preguntas sobre el Assistant? + +Tus preguntas y comentarios son bienvenidos en +[community.openfn.org](https://community.openfn.org/), o contacta a +[support@openfn.org](mailto:support@openfn.org) para consultas privadas. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/channels.md b/i18n/es/docusaurus-plugin-content-docs/current/build/channels.md new file mode 100644 index 000000000000..391f32fa2341 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/channels.md @@ -0,0 +1,263 @@ +--- +title: Canales +sidebar_label: Canales +translation_source_hash: 5d829d4c4243d09a5e89ee9020f2bd22c3a73921 +translation_review_status: machine +--- + +Los canales convierten OpenFn en un **proxy inverso**: un intermediario seguro +entre dos sistemas. En lugar de conectar una aplicación cliente directamente a +un servicio de destino, el cliente envía sus solicitudes a OpenFn, y OpenFn las +reenvía, se encarga de la autenticación y registra cada solicitud en el camino. + +``` +Mobile App → OpenFn Channel → Health Registry +(client) (the middleman) (destination) +``` + +Obtienes visibilidad inmediata de todo lo que pasa por el canal, sin escribir +código de workflows. A diferencia de los workflows, los canales son un simple +_paso directo_: OpenFn no transforma los datos, sino que los enruta, los protege +y los observa. + +Los canales están pensados para organizaciones que necesitan una capa de proxy +para la seguridad, la observabilidad y el control de acceso en sus intercambios +de datos, como los intercambios de información de salud que históricamente han +usado una herramienta independiente como OpenHIM para este fin. Los canales +ofrecen esa funcionalidad básica de proxy inverso de forma nativa dentro de +OpenFn. + +:::tip Funcionalidad experimental + +Los canales son actualmente una funcionalidad experimental. Para usarlos, activa +**Experimental Features** en la página de tu +[perfil de usuario](/manage-users/user-profile.md). Si no ves el elemento +`Channels` en la barra lateral de tu proyecto, el motivo es esta opción. + +::: + +## Cómo funciona {#how-it-works} + +Cuando un cliente envía una solicitud HTTP a la URL de proxy de tu canal, +OpenFn: + +1. Recibe la solicitud en `/channels/{channel-id}/{path}` +2. Busca el canal y comprueba que esté activado +3. Autentica al cliente, si hay credenciales de cliente configuradas +4. Reenvía la solicitud a `{destination-url}/{path}`, conservando el método, el + cuerpo, los encabezados y los parámetros de consulta +5. Agrega los encabezados `x-forwarded-for`, `x-forwarded-host`, + `x-forwarded-proto` y `x-request-id` para que el destino pueda rastrear la + solicitud +6. Adjunta un encabezado `Authorization` para el destino, si hay una credencial + de destino configurada +7. Devuelve la respuesta del destino directamente al cliente, en streaming +8. Registra la solicitud en `History` → `Channel Logs` + +Se admiten todos los métodos HTTP estándar: `GET`, `POST`, `PUT`, `PATCH`, +`DELETE` y otros. + +Por seguridad, OpenFn nunca reenvía cookies al destino, y las credenciales que +el cliente usó para autenticarse _ante OpenFn_ (el encabezado `Authorization` +para Basic Auth, o el encabezado `x-api-key` para las claves de API) se eliminan +antes de pasar la solicitud. + +## Antes de empezar {#before-you-start} + +Necesitas: + +- La opción **Experimental Features** activada en tu perfil de usuario +- Un **proyecto** en el que tengas el + [rol](/manage-projects/user-roles-permissions.md) `Owner`, `Admin` o `Editor` + (los usuarios con rol Viewer pueden ver los canales y sus logs, pero no pueden + crearlos ni modificarlos) +- La **URL del servicio de destino** al que quieres hacer de proxy (una API + pública como `https://hacker-news.firebaseio.com/v0` funciona muy bien para + hacer pruebas) + +## Paso 1: configura las credenciales (opcional) {#step-1-set-up-credentials-optional} + +Los canales usan dos tipos de credenciales, y ambos son opcionales: + +- Las **credenciales de cliente** controlan quién puede enviar solicitudes _a tu + canal_. Son los mismos + [métodos de autenticación de webhooks](/manage-projects/webhook-auth.md) que + se usan para proteger los triggers webhook (Basic HTTP Authentication o API + Key Authentication) y se gestionan en `Webhook Security`, en la configuración + de tu proyecto. +- Una **credencial de destino** es la forma en que OpenFn se autentica _ante el + servicio de destino_. Es una [credencial de proyecto](/build/credentials.md) + normal, y OpenFn la usa para construir el encabezado `Authorization` en cada + solicitud reenviada. Actualmente, los canales admiten estos tipos de + credenciales: + +| Tipo de credencial | Encabezado que se envía al destino | +| ------------------ | --------------------------------------------------- | +| HTTP | Token `Bearer`, o Basic Auth (usuario y contraseña) | +| DHIS2 | `ApiToken`, o Basic Auth (usuario y contraseña) | +| OAuth | Token `Bearer`, que OpenFn renueva automáticamente | + +:::tip + +Si solo quieres hacer pruebas con un endpoint público, sáltate este paso por +completo: no necesitas credenciales. + +::: + +## Paso 2: crea un canal {#step-2-create-a-channel} + +1. Ve a tu proyecto +2. Haz clic en `Channels` en la barra lateral izquierda +3. Haz clic en `New Channel` +4. Completa el formulario: + +| Campo | Qué poner | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Name | Un nombre para identificar este canal (debe ser único dentro del proyecto) | +| Enabled | Debe estar activado para que el canal acepte solicitudes | +| Destination URL | La URL base del servicio al que OpenFn reenviará las solicitudes | +| Destination Credential | Cómo se autentica OpenFn ante el servicio de destino (déjalo en `None` si el destino es público) | +| Client Credentials | Marca los métodos de autenticación de webhooks que los clientes pueden usar para acceder a este canal (déjalos sin marcar para permitir solicitudes sin autenticar durante las pruebas) | + +5. Haz clic en `Save` + +Una vez guardado, tu canal aparece en la lista de canales con su **URL de +proxy**. Haz clic en la URL para copiarla al portapapeles. Tiene este aspecto: + +``` +https://your-openfn-instance.com/channels/{channel-id} +``` + +## Paso 3: envía una solicitud a través del canal {#step-3-send-a-request-through-the-channel} + +Envía una solicitud HTTP a la URL de proxy de tu canal y agrega al final la ruta +del destino a la que quieras llegar: + +``` +https://your-openfn-instance.com/channels/{channel-id}/{path} +``` + +OpenFn la reenvía a `{destination-url}/{path}`. + +### Ejemplo con curl {#example-using-curl} + +Supongamos que la URL de destino de tu canal es +`https://hacker-news.firebaseio.com/v0`. Para obtener una noticia de Hacker News +a través de tu canal: + +```bash +curl https://app.openfn.org/channels/{channel-id}/item/8863.json +``` + +OpenFn recibe la solicitud, la reenvía a +`https://hacker-news.firebaseio.com/v0/item/8863.json` y te devuelve la +respuesta. + +### Más ejemplos {#more-examples} + +**POST con un cuerpo:** + +```bash +curl -X POST https://app.openfn.org/channels/{channel-id}/patients \ + -H "Content-Type: application/json" \ + -d '{"patient_id": "123", "status": "admitted"}' +``` + +**Con parámetros de consulta:** + +```bash +curl "https://app.openfn.org/channels/{channel-id}/patients?status=admitted" +``` + +**Con una credencial de cliente** (si configuraste una): + +```bash +# Basic Auth +curl -u username:password https://app.openfn.org/channels/{channel-id}/patients + +# API key +curl -H "x-api-key: your-api-key" https://app.openfn.org/channels/{channel-id}/patients +``` + +## Paso 4: consulta los logs {#step-4-view-the-logs} + +Cada solicitud que pasa por un canal queda registrada. + +1. Ve a la página `History` de tu proyecto +2. Haz clic en la pestaña `Channel Logs` +3. Verás cada solicitud en la lista, con su Request ID, la ruta de la solicitud, + el nombre del canal, la hora de inicio, el estado y el mensaje de error, si + lo hay + +Haz clic en una solicitud para abrir su página de detalle completa, que muestra +los encabezados y una vista previa del cuerpo de la solicitud y de la respuesta, +la información de tiempos y la configuración que tenía el canal cuando se hizo +la solicitud. Los encabezados sensibles (como `Authorization`) aparecen ocultos +en los logs. + +Cada solicitud tiene uno de estos estados: + +| Estado | Significado | +| ------- | ------------------------------------------------------------------------- | +| Pending | La solicitud todavía está en curso | +| Success | El destino respondió con un código de estado `2xx` | +| Failed | El destino respondió con un código de estado `4xx` o `5xx` | +| Timeout | El destino no respondió a tiempo | +| Error | No se pudo completar la solicitud (por ejemplo, por un error de conexión) | + +Si tienes varios canales, usa el filtro **Channel** para limitar la lista a un +solo canal. También puedes ir directamente a los logs filtrados de un canal +haciendo clic en su cantidad de **Requests** o en su **Last Activity** en la +página Channels. + +:::info + +Que se guarden o no los payloads de las solicitudes y las respuestas depende de +la configuración de [Data Storage](/manage-projects/io-data-storage.md) de tu +proyecto. Si tu proyecto no guarda los datos de entrada y salida, los metadatos +de las solicitudes del canal se siguen registrando, pero los payloads se borran. + +::: + +## Consideraciones de seguridad {#security-considerations} + +- El endpoint del proxy es **accesible públicamente**. Si no hay credenciales de + cliente configuradas, cualquiera que conozca la URL del canal puede enviar + solicitudes a través de él. Configura siempre credenciales de cliente para los + canales de producción. +- Al cambiar un canal a **deshabilitado**, deja de aceptar solicitudes de + inmediato (los clientes reciben un `404`). +- Cada solicitud se registra junto con una instantánea de la configuración que + tenía el canal en ese momento, así que tienes un registro de auditoría incluso + después de que el canal cambie. +- Un canal con historial de solicitudes no se puede eliminar, porque hay que + conservar su historial; deshabilítalo en su lugar. + +## Limitaciones {#limitations} + +- Los canales solo hacen de proxy para tráfico HTTP(S); no se admite el paso + directo de TCP sin procesar ni de TLS +- La ruta de la solicitud se reenvía tal cual; no se admite transformar la ruta +- No se admite la auditoría ATNA + +## Solución de problemas {#troubleshooting} + +| Problema | Causa probable | Solución | +| ----------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `404` en la URL del canal | El canal está deshabilitado, o el ID del canal es incorrecto | Comprueba que el canal esté habilitado y que el ID coincida | +| `401` en la URL del canal | Hay credenciales de cliente configuradas y tu solicitud no coincide con ninguna | Envía las credenciales correctas con tu solicitud, o desmarca las credenciales de cliente para hacer pruebas | +| `502` en la URL del canal | OpenFn no pudo usar la credencial de destino (por ejemplo, hay que volver a autorizarla) | Revisa la credencial de destino y el mensaje de error en `Channel Logs` | +| La solicitud pasa, pero el destino devuelve un error | La URL de destino o la ruta es incorrecta | Revisa bien la URL de destino y la ruta que agregas al final | +| No aparece el elemento `Channels` en la barra lateral | La opción Experimental Features está desactivada | Habilita `Experimental Features` en tu perfil de usuario | + +## Referencia rápida {#quick-reference} + +| Qué | Dónde encontrarlo | +| ----------------------------- | ----------------------------------------------------------------- | +| Crear y gestionar canales | `Project` → `Channels` | +| URL de proxy | Haz clic para copiarla desde la lista de canales, o abre el canal | +| Patrón del endpoint del proxy | `https://{instance}/channels/{channel-id}/{path}` | +| Ver los logs | `Project` → `History` → pestaña `Channel Logs` | +| Logs filtrados de un canal | Haz clic en la cantidad de `Requests` en la página Channels | +| Credenciales de cliente | `Project Settings` → `Webhook Security` | +| Credenciales de destino | `Project Settings` → `Credentials` | diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/collections.md b/i18n/es/docusaurus-plugin-content-docs/current/build/collections.md new file mode 100644 index 000000000000..eda2c841024e --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/collections.md @@ -0,0 +1,207 @@ +--- +title: Colecciones +sidebar_label: Colecciones +translation_source_hash: bdf77a9b1e55431a03704daec4224dda3b396dbf +translation_review_status: machine +--- + +Las colecciones son una solución de almacenamiento de gran volumen y alto +rendimiento integrada en OpenFn. Mira +**[este video](https://www.youtube.com/watch?v=iXkkkzratzY&t=3s&ab_channel=OpenFn.org)** +para una introducción. + +Las colecciones sirven para almacenar en búfer, guardar en caché y agregar datos +de webhooks, guardar archivos de mapeo grandes y compartir state entre +workflows. + +En las colecciones se puede guardar una cantidad muy grande de elementos (del +orden de millones). + +## Casos de uso {#use-cases} + +### Almacenar datos en búfer {#buffering-data} + +Muchas integraciones de OpenFn se disparan con un webhook, al que llama otro +sistema a partir de algún evento. Por ejemplo, cada vez que se registra un +paciente, un webhook llama a OpenFn para disparar un workflow y propagar el +evento de registro a otros sistemas. + +Las colecciones pueden servir de búfer para estos eventos entrantes: guardan los +datos del evento en OpenFn para procesar después un lote de eventos al final del +día. Esto es especialmente útil con eventos de gran volumen, o cuando los +sistemas de origen tienen límites. + +Con las colecciones, puedes guardar cada evento entrante en OpenFn y luego +ejecutar un workflow con un trigger cron que procese un lote de eventos de una +sola vez y envíe los resultados agregados, filtrados o transformados al +siguiente sistema. + +### Estructuras de mapeo {#mapping-structures} + +Un caso de uso típico en las integraciones de datos es almacenar objetos de +mapeo grandes. Estos objetos son pares clave-valor que asignan cadenas de un +sistema a las cadenas correspondientes de otro sistema. Por ejemplo, mapear +códigos médicos a SNOMED, mapear códigos de ciudad a cadenas legibles para las +personas o mapear una cadena de entrada a un código de atributo de DHIS2. + +Estos objetos suelen ser muy grandes y difíciles de mantener, y pueden inflar el +código del job. + +En cambio, los mapeos se pueden guardar como un objeto JSON en un repositorio de +GitHub y subirse a una colección con la CLI. + +## Conceptos básicos de las colecciones {#collections-basics} + +:::tip + +La API de colecciones está disponible automáticamente para todos los workflows y +no necesita credenciales. La autenticación con la plataforma OpenFn se gestiona +por ti. + +Puedes usar la API de colecciones con cualquier adaptor. + +::: + +Los datos se guardan como pares clave-valor, donde la clave es un identificador +único de ciertos datos (como un UUID o una marca de tiempo). El valor siempre se +guarda como cadena (aunque puedes pasar directamente objetos compatibles con +JSON, que la API de colecciones serializa automáticamente). + +Las claves se pueden obtener en bloque y filtrar por _patrón_. Por ejemplo, el +patrón `2024*` coincide con todas las claves que empiezan con `2024`. En las +aplicaciones de colecciones de gran volumen, es fundamental diseñar las claves +para que tengan un orden de clasificación eficiente. + +El siguiente ejemplo obtiene valores de la colección +`openfn-patient-registrations` y los guarda en el state para procesarlos +después: + +```js +collections.get('openfn-patient-registrations', '2024*').then(state => { + state.registrationsThisYear = state.data; + return state; +}); +``` + +Los elementos devueltos se escriben en state.data como un array de pares +`[{ key, value }]`: + +```js +{ + "data": { + "20240102-5901257": { + "name": "Tom Waits", + "id": "5901257", + }, + "20240213-0183216": { + "name": "Billie Holiday", + "id": "0183216", + } + } +} +``` + +Si obtienes un solo elemento (es decir, sin `*` en la clave), se escribe +directamente en `state.data`, sin clave: + +```js +{ + "data": { + "name": "Billie Holiday", + "id": "0183216", + } +} +``` + +Cada clave guarda de forma permanente su fecha de creación, así que, además de +obtenerlas por patrón de clave, también puedes filtrar las claves por fecha. +Este ejemplo obtiene todas las claves creadas antes del 30 de septiembre de +2024: + +```js +collections + .get('openfn-patient-registrations', '*', { createdBefore: '2024-09-30' }) + .then(state => { + state.registrationsThisYear = state.data; + return state; + }); +``` + +`collections.get` descarga en memoria todos los valores que coinciden. Para +valores grandes o conjuntos de valores de gran volumen, es más eficiente usar +`collections.each`, que carga cada valor en memoria por separado, en streaming, +y luego lo descarta. + +```js +collections.each( + 'my-collection', + { key: '2024*', createdAfter: '20240601' }, + (state, value, key) => { + console.log(value); + } +); +``` + +Los valores se suben a una colección con `collections.set`. Todas las +asignaciones son "upserts": se crean claves nuevas para los valores que no +existen y se actualizan los valores de las claves que _sí_ existen. + +El siguiente ejemplo asigna un solo elemento: + +```js +collections.set('openfn-demo', 'commcare-fhir-value-mappings', { + current_smoker: { + system: 'http://snomed.info/sct', + code: '77176002', + display: 'Smoker', + }, + /* ... */ +}); +``` + +Si asignas varios valores a la vez, pasa una función generadora de claves en +lugar de un id, para generar una clave para cada elemento. Por ejemplo, si se +guardan varios valores en un array en `state.data`: + +```js +collections.set('openfn-demo', (patient, state) => patient.id, $.patients); +``` + +La función generadora de claves se llama con cada valor y debe devolver una +clave de tipo cadena. + +## Gestionar colecciones {#managing-collections} + +Las colecciones se pueden crear, eliminar o renombrar desde el menú Admin. + +![Página de administración de colecciones](/img/collections_admin.webp) + +Antes de poder usar una colección, hay que crearla. Los nombres de las +colecciones deben ser únicos en el despliegue, así que recomendamos usar como +prefijo tu organización (y quizás el proyecto), por ejemplo, `openfn-demo`. + +## Usar colecciones {#using-collections} + +Las colecciones están disponibles para todos los workflows mediante una interfaz +sencilla de alto nivel. + +:::caution + +Hay que crear una colección en la interfaz de administración antes de poder +usarla. + +::: + +La API de colecciones ofrece cuatro verbos básicos: + +- [`collections.get()`](/adaptors/packages/collections-docs#collections_get) + descarga los valores que coinciden con una clave o un patrón de clave. +- [`collections.each()`](/adaptors/packages/collections-docs#collections_each) + recorre de forma eficiente un rango de elementos de una colección. +- [`collections.set()`](/adaptors/packages/collections-docs#collections_set) + sube valores a una colección. +- [`collections.remove()`](/adaptors/packages/collections-docs#collections_remove) + elimina valores por clave o patrón de clave. + +La API de colecciones se apoya en un adaptor especial: consulta la +[API del adaptor de colecciones](/adaptors/collections) para más detalles. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/credentials.md b/i18n/es/docusaurus-plugin-content-docs/current/build/credentials.md new file mode 100644 index 000000000000..1020914ff4ea --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/credentials.md @@ -0,0 +1,142 @@ +--- +title: Credenciales +translation_source_hash: 5b3d2b9a4a5e7e4ec6cae6b4870ae4a718dfc5fb +translation_review_status: machine +--- + +Las credenciales sirven para autorizar conexiones con sistemas externos. Algunos +adaptors usan credenciales para obtener metadatos de las aplicaciones de origen +y de destino, lo que facilita escribir jobs. + +Los valores de una credencial solo los puede ver o editar un único usuario: su +"propietario" (el usuario que creó esa credencial). Todos los colaboradores de +un proyecto pueden elegir entre todas las credenciales del proyecto al definir +un job. + +![Página de credenciales](/img/settings_credentials.webp) + +### Crear una credencial nueva {#create-a-new-credential} + +Puedes crear una credencial nueva mientras configuras un step nuevo en tu +workflow, o desde la página Settings > Credentials. +[Lee esta guía](/manage-projects/manage-credentials.md) para saber más sobre +cómo gestionar credenciales. + +### Entender las credenciales de cada aplicación {#understand-the-app-specific-credentials} + +Consulta la página de [documentación de adaptors](/adaptors) dedicada a tu +aplicación para revisar el `configuration schema` y ver qué datos de la +credencial se necesitan para autenticarte en tu aplicación (por ejemplo, +`username`, `api_key`). + +Si tu aplicación no aparece en la sección de adaptors, revisa la documentación +de su API para ver qué se necesita para la "autenticación". Luego puedes crear +en OpenFn una credencial `Raw JSON` para definir los datos de credencial que +hagan falta. Por ejemplo: + +```json +{ + "apiKey": "someSecretKey", + "baseUrl": "https://example.com/api/v2" +} +``` + +Ten en cuenta que algunos sistemas (Salesforce, OpenMRS, DHIS2) requieren un +instanceUrl, host o ApiUrl. Aunque la mayoría de los adaptors manejan bien una +"barra final" en una URL, si tienes dudas, es mejor no ponerla. Por ejemplo: + +- prefiere `https://login.salesforce.com` a `https://login.salesforce.com/`, +- usa `http://demo.openmrs.org/openmrs` en lugar de + `http://demo.openmrs.org/openmrs/`, +- y escribe `https://play.dhis2.org` en vez de `https://play.dhis2.org/`. + +### Usar credenciales OAuth2 {#use-oauth2-credentials} + +Si en tu instancia de OpenFn se configuraron _clientes_ OAuth2, puedes usarlos +para crear credenciales OAuth: + +1. Primero, elige un tipo de credencial OAuth en la interfaz "New Credential". +2. Luego, ponle un nombre. +3. Opcionalmente, selecciona los "scopes" adicionales que quieras usar. (Según + la aplicación que uses, consulta el enlace a la documentación del tercero + sobre scopes que aparece en la aplicación). +4. Por último, haz clic en "Sign in with \_\_\_\_\_\_". + +La aplicación del tercero te pedirá que verifiques tu identidad y que confirmes +que quieres usar OAuth. Cuando aceptes, de vuelta en OpenFn podrás guardar y +usar tu nueva credencial como cualquier otra. + +:::tip + +Si usas una instancia desplegada de OpenFn y no encuentras el tipo de credencial +OAuth para tu aplicación, contacta al superusuario responsable de configurar tu +instancia y pídele que configure clientes OAuth para tu aplicación. Si usas la +plataforma SaaS alojada de OpenFn, puedes publicar en +[community.openfn.org](https://community.openfn.org) o escribir a +[support@openfn.org](mailto:support@openfn.org). + +::: + +#### Por ejemplo: credencial OAuth de Google Sheets {#eg-googlesheets-oauth-credential} + +Observa que la credencial selecciona solo los scopes necesarios para Google +Sheets. + +![Credencial OAuth de Google Sheets](/img/gsheets-oauth2.webp) + +#### Por ejemplo: credencial OAuth de Salesforce {#eg-salesforce-oauth-credential} + +Observa que puedes elegir a qué scopes acceder en Salesforce. + +![Credencial OAuth de Salesforce](/img/salesforce-oauth2.webp) + +:::tip + +Consulta la página de [documentación de adaptors](/adaptors) de tu aplicación +para ver indicaciones sobre sus credenciales. + +::: + +### Crear un "usuario de integración" dedicado para tu workflow de OpenFn {#creating-a-dedicated-integration-user-for-your-openfn-workflow} + +Para que los sistemas de destino sean lo más seguros y controlados posible, +recomendamos que las credenciales que se usan en la integración tengan acceso +solo por API a la aplicación de destino. + +_Puedes_ usar tu usuario personal como credencial de OpenFn para tu workflow, +pero recomendamos crear un usuario de integración "OpenFn" dedicado o un usuario +de cuenta de servicio para acceder a tus aplicaciones de destino. Por ejemplo, +en [Salesforce](/adaptors/salesforce#salesforce-credentials) puedes crear un +usuario solo de API con un tipo de licencia especial solo de API para realizar +tareas automatizadas e integraciones sin necesitar acceso completo de usuario. +Para las API de Google, como +[Google Sheets](/adaptors/googlesheets#using-a-google-service-account), lo +recomendable para los workflows automatizados es una cuenta de servicio de +Google. + +Puede que no todos los sistemas de destino ofrezcan usuarios solo de API, pero +muchos permiten crear roles de usuario con permisos de acceso solo por API, y es +posible que te dejen definir a qué API o endpoints pueden acceder los usuarios. +Incluso cuando el sistema de destino no ofrece un usuario solo de API, las +buenas prácticas indican usar un usuario de integración o de servicio para todas +las tareas de automatización, para mantener un registro de auditoría seguro. + +El acceso solo por API reduce el riesgo de filtraciones de datos porque: + +- **Garantiza la trazabilidad**: acceder con un usuario de integración deja un + registro de auditoría de quién inició sesión, cuándo y qué cambios hizo. Por + ejemplo, si usaras tu usuario personal para un sistema en una implementación + de integración, sería difícil saber si fuiste TÚ, una persona, quien hizo un + cambio, o si fue una acción automatizada del sistema a través del usuario de + API. + +- **Minimiza el impacto de una filtración**: si el usuario se ve comprometido, + se puede desactivar, y el inicio de sesión en el frontend con la credencial de + API filtrada se bloquea automáticamente, lo que limita los vectores de ataque. + +- **Aplica el principio de mínimo privilegio**: cada usuario de integración solo + necesita acceso al subconjunto de datos que requiere su caso de uso + específico. + +Consulta la documentación sobre +[buenas prácticas de seguridad](/get-started/security.md) para saber más. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/editing-locally.md b/i18n/es/docusaurus-plugin-content-docs/current/build/editing-locally.md new file mode 100644 index 000000000000..312dfe07fa99 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/editing-locally.md @@ -0,0 +1,79 @@ +--- +title: Editar steps localmente +sidebar_label: Editar steps localmente +translation_source_hash: 17d7dee86170ea20aa87a3e41929b8bcded068d2 +translation_review_status: machine +--- + +Si eres desarrollador, puedes usar tu editor de texto favorito y hacer cambios +sin conexión, con commits y push a GitHub. Esta página explica cómo editar steps +localmente, en lugar de hacerlo en la plataforma con +[el Inspector](/build/steps/step-editor.md). + +Primero, asegúrate de que el control de versiones esté configurado para tu +proyecto (consulta [Gestionar proyectos](/manage-projects/platform-mgmt.md) para +saber cómo configurarlo). Cuando esté listo, sigue estos pasos en tu +computadora: + +1. Asegúrate de tener + [git instalado](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + +2. Clona el repositorio desde GitHub. Según cómo te conectes, copia la URL HTTPS + o SSH del repositorio. + +![URL para clonar en GitHub](/img/git_clone_url.webp) + +:::tip + +Puedes conectarte a GitHub con usuario y contraseña (HTTPS) o con un par de +claves SSH que hayas generado. (Consulta la +[documentación de GitHub](https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories) +para más información). + +::: + +3. Luego, úsala para + [clonar el repositorio](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) + en tu computadora; para ello, ejecuta este comando en la carpeta donde + quieras guardar el nuevo repositorio: `git clone {repo URL}` (por ejemplo, + `git clone https://github.com/OpenFn/Miracle-Feet.git`) + +4. Para actualizar tu copia local con los cambios de GitHub, ejecuta `git pull` + con frecuencia mientras editas. + +5. En este tutorial, suponemos que haces los cambios en la rama `main` o + `master`: la que está desplegada en OpenFn como tu sistema de producción. + +6. Para editar tus steps, usa un editor de código. Recomendamos + [Visual Studio Code](https://code.visualstudio.com/download). + +![VS Code](/img/edit_job_vscode.webp) + +7. Si usas VS Code, asegúrate de instalar la + [extensión Prettier para VS Code](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + y configurarla como formateador predeterminado en Settings, como se ve abajo. + Así se aplica el formato de código correcto a los archivos que cambies. + +![Prettier](/img/prettier.webp) + +8. Cuando termines, puedes ver qué archivos cambiaste con `git status`. + +9. Luego, usa `git add {filepath}` y después `git commit -m {change notes}` para + preparar los cambios para hacer merge en el repositorio. + +:::tip + +Hay mucho que aprender sobre git. +[Este es un buen punto de partida](https://github.com/git-guides/git-commit). + +::: + +10. Luego, ejecuta `git push` para subir los archivos al repositorio (consulta + más en la [documentación de git](https://github.com/git-guides/git-push)). + +A partir de ahí, la integración con el control de versiones actualizará los +steps modificados en tu proyecto de OpenFn y podrás probar esos cambios en la +plataforma. + +Cuando quieras empezar a ejecutar steps y probar tus cambios _localmente_, +consulta la documentación de la [CLI](/build-for-developers/cli-intro.md). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/limits.md b/i18n/es/docusaurus-plugin-content-docs/current/build/limits.md new file mode 100644 index 000000000000..7dfe6324bf95 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/limits.md @@ -0,0 +1,87 @@ +--- +title: Límites +translation_source_hash: 322601a39382248bb796b091a3a0c3e3e7273be2 +translation_review_status: machine +--- + +La instancia de OpenFn alojada en la nube tiene varios límites que ayudan a que +todo funcione sin problemas. La siguiente tabla muestra los límites de los +distintos planes. Para ver una lista de límites más detallada, consulta la +[página de precios de OpenFn](https://openfn.org/pricing). En las instancias +autoalojadas, estos límites se pueden configurar. Consulta la +[guía de despliegue](https://openfn.github.io/lightning/deployment.html#limits) +para más detalles. + +| Funcionalidad | Descripción | DPG | Free | Core | Growth | Scale | Unlimited | +| ------------------------------------- | ---------------------------------------------------------------------------------------- | ------------ | ----- | ----- | ------ | --------- | --------- | +| Runs | Número máximo de runs permitidos por mes | Ilimitado | 100 | 2000 | 5000 | 10 000 | Ilimitado | +| Duración de ejecución del workflow | Tiempo máximo que puede ejecutarse un workflow antes de que se detenga | Configurable | 60 s | 5 min | 20 min | 30 min | 30 min | +| Uso de memoria | Memoria máxima permitida por attempt del workflow | Configurable | 128MB | 256MB | 512MB | 1GB | 1GB | +| Tamaño del state | Tamaño máximo de los objetos state dentro de la VM del runtime (25 % del uso de memoria) | Dinámico | 32MB | 64MB | 128MB | 256MB | 256MB | +| Tamaño de los dataclips | Tamaño máximo de los dataclips guardados a partir de la salida de un run | Configurable | 512KB | 2MB | 10MB | 10MB | 10MB | +| AI Assistant | Máximo de tokens de IA disponibles | Configurable | 500K | 1.5M | 5M | 10M | 10M | +| Colecciones de datos (almacenamiento) | Almacenamiento máximo para colecciones de datos | Configurable | 1MB | 5MB | 10MB | 50MB | 50MB | +| Colecciones de datos (cantidad) | Número máximo de colecciones de datos por proyecto | Configurable | 2 | 5 | 10 | Ilimitado | Ilimitado | +| Control de concurrencia | Permite a los usuarios controlar los límites de concurrencia del proyecto | Configurable | Sí | Sí | Sí | Sí | Sí | + + + +:::tip Aumentar los límites en instancias alojadas en la nube y gestionadas + +En los planes estándar, puedes aumentar tus límites mejorando a un plan superior +con las +[instrucciones para mejorar tu plan](/hosted/overview.md#upgrading-your-subscription). + +Para límites personalizados o mejoras en despliegues dedicados, escribe a +enterprise@openfn.org. + +::: + +## Duración de ejecución del workflow (1 hora) {#workflow-execution-duration-1-hour} + +Cada attempt de un workflow debe completarse en menos de `1 hour`. Puedes ver la +duración de cada attempt haciendo clic en su ID. Si un attempt supera este +límite, el worker lo detiene y verás la insignia `Killed:Timeout` como estado +del attempt. + +> _Los superusuarios de la instancia pueden controlar este límite con la +> variable de entorno `MAX_RUN_DURATION`._ + +## Uso de memoria (1GB) {#memory-usage-1gb} + +Cada attempt de un workflow no puede usar más de `1GB` de memoria. Puedes ver el +uso máximo de memoria de cada attempt haciendo clic en su ID. Si un attempt +supera este límite, el worker lo detiene y verás la insignia `Killed:OOM` como +estado del attempt. + +> _Los superusuarios de la instancia pueden controlar este límite con la +> variable de entorno `MAX_RUN_MEMORY`._ + +Ten en cuenta que el objeto `state` que se devuelve al final de cada step de un +workflow no debe superar el 25 % del límite total de memoria del runtime, o tu +run se detendrá con un error `StateTooLarge`. + +## Tamaño de los dataclips (10MB) {#dataclip-size-10mb} + +1. Cada **solicitud de webhook** a una URL de trigger no puede superar `10MB`. +2. Si guardas el state final de cada **run** como dataclip, cada dataclip no + puede superar `10MB`. + + + + +Si envías a una URL de trigger webhook un payload que supera este límite, el +servidor responde con un error `413` y el mensaje `:request_entity_too_large`. + +Si los dataclips que genera el state final de los runs y los attempts son +demasiado grandes, no se guardan. El worker igual procesa los steps siguientes, +pero esos steps no se podrán reintentar, porque Lightning no guarda una copia de +los dataclips. Verás el error `ERROR: DataClip too large for storage` en los +logs del attempt. + +> _Los superusuarios de la instancia pueden controlar este límite con la +> variable de entorno `MAX_DATACLIP_SIZE`._ diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/paths.md b/i18n/es/docusaurus-plugin-content-docs/current/build/paths.md new file mode 100644 index 000000000000..cbcf13625783 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/paths.md @@ -0,0 +1,69 @@ +--- +title: Paths y condiciones de los paths +sidebar_label: Paths +translation_source_hash: f761fd26aaa7d8373dd681c3fd033f90dd0fef8a +translation_review_status: machine +--- + +Un path es una indicación, visual y funcional a la vez, que define la secuencia +de steps que sigue el workflow cuando se ejecuta. Sigue leyendo para conocer los +distintos tipos de paths y algunos consejos de configuración. + +## Condiciones de los paths {#path-conditions} + +Hay cuatro tipos de condiciones de path que definen si el workflow pasará al +siguiente step al ejecutarse: + +1. **Always**: el siguiente step siempre se ejecuta cuando termina la ejecución + del step anterior +2. **On Success**: el siguiente step solo se ejecuta si la ejecución del step + anterior _tuvo éxito_ +3. **On Failure**: el siguiente step solo se ejecuta si la ejecución del step + anterior _falló_ +4. **Matches a JavaScript Expression**: el siguiente step solo se ejecuta si una + expresión se evalúa como verdadera + +![Condiciones de los paths](/img/path_conditions.webp) + +## Escribir expresiones de JavaScript para condiciones de path personalizadas {#writing-javascript-expressions-for-custom-path-conditions} + +Crea una **condición personalizada** con una expresión de JavaScript. Se evalúa +con el state que produjo el step anterior. + +``` +state.data.form['@name'] === "Register New Patient" +``` + +Es una expresión normal de JavaScript con `state` en el ámbito. Si la expresión +se evalúa como verdadera (o como cualquier valor _truthy_), se sigue el path y +se ejecuta el siguiente step. + +![Condiciones personalizadas](/img/path_js_expression.webp) + +Algunos ejemplos de condiciones válidas: + +- Ejecutar si no hay errores: `!state.errors` +- Ejecutar si existe algún valor en el state: `state.has_valid_email_address` +- Ejecutar si un array de datos contiene algún elemento: `state.data.length > 0` +- Ejecutar si los datos incluyen un elemento que cumple un criterio: + `state.data.includes(item => item.age > 18)` +- Ejecutar si el último step recibió un error HTTP: + `state.response.statusCode >= 400` + +En una expresión personalizada **no puedes** hacer nada de lo siguiente: + +- Usar funciones de adaptors +- Usar referencias de lazy state (`$`). +- Usar sentencias de control como `if`, `while`, `for`, etc. + +## Deshabilitar paths {#disabling-paths} + +Deshabilitar un path impide que se ejecute cualquiera de los steps posteriores, +sin importar la condición ni el state. + +Es una forma útil de desactivar temporalmente una parte de tu workflow. + +Para deshabilitar un path: + +1. Haz clic en el `Path` que quieres desactivar +2. Marca la casilla `Disable this path` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/sandboxes.md b/i18n/es/docusaurus-plugin-content-docs/current/build/sandboxes.md new file mode 100644 index 000000000000..e33353d4a725 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/sandboxes.md @@ -0,0 +1,327 @@ +--- +title: Sandboxes +sidebar_label: Sandboxes +translation_source_hash: 45f6e0cb7820464901f65a1cb95e99a8f3cea5d0 +translation_review_status: machine +--- + +Los sandboxes te permiten desarrollar correcciones y funcionalidades nuevas en +tus workflows sin afectar los runs en vivo, o de "producción". + +Un sandbox es, en esencia, un clon de un proyecto, con su propio historial +privado, workflows, colecciones y configuración. Comparte las credenciales y la +facturación del proyecto principal, pero todo lo demás está aislado. + +La idea es que puedas desarrollar workflows totalmente aislados del proyecto +principal y, cuando termines, hacer merge de los cambios de vuelta (entiéndase +"enviarlos" o "promoverlos"). + +:::tip Sandboxes de corta duración + +Los sandboxes funcionan mejor cuando duran poco, por eso actualmente se +programan automáticamente para eliminarse después de hacer merge. Aunque puedes +crear todos los sandboxes que quieras (según el límite de uso de tu plan), +recomendamos tener pocos para reducir el riesgo de conflictos al hacer merge. + +::: + +## Contexto aislado {#isolated-context} + +Un sandbox es una copia aislada de tu proyecto original, con su propio contexto. +La mayoría de las cosas de un sandbox son privadas: + +- Workflows (jobs, edges y triggers; los triggers se deshabilitan al crearlo) +- Colecciones (se copian los nombres, no los datos) +- Credenciales de Keychain +- Miembros del proyecto (se copian del proyecto principal al crearlo y después + son independientes) +- La mayor parte de la configuración del proyecto (también se copia del proyecto + principal y después es independiente) + +El historial de runs y los dataclips no se copian: los sandboxes empiezan con un +historial vacío y acumulan el suyo a medida que se ejecutan los workflows. + +Algunas cosas se comparten con el proyecto principal en lugar de copiarse: + +- **Credenciales.** Las mismas credenciales siguen disponibles en el sandbox; + solo se duplica el vínculo entre la credencial y el proyecto. Editar una + credencial afecta a todos los proyectos que la usan. +- **Suscripción.** Los runs y los tokens de IA que se usan en un sandbox cuentan + para los límites de uso del proyecto principal. +- **Métodos de autenticación de webhook.** Están en el proyecto principal y se + comparten: los triggers webhook del sandbox hacen referencia a los métodos del + proyecto principal. La pestaña Webhook Security del sandbox es de solo lectura + y enlaza al proyecto principal para gestionarlos. Cada trigger webhook del + sandbox sigue teniendo su propia URL única. + +## Crear sandboxes {#creating-sandboxes} + +Para crear un sandbox, debes ser `editor`, `admin` u `owner` del proyecto +principal. Entra al proyecto principal, haz clic en **Sandboxes** en el menú +lateral y luego en **Create Sandbox**. + +![Lista de sandboxes](/img/sandboxes_list.webp) + +Tienes que ponerle un nombre al sandbox. Debe ser único entre los sandboxes del +proyecto. Si conoces git, trátalo como el nombre de una rama. Si no, puedes +darle un nombre general, como `testing`, o nombrarlo según una funcionalidad +concreta, como `new-patient-workflow`. + +Se elige un color al azar para asociarlo al sandbox. Verás ese color en el +selector de proyectos de la ruta de navegación mientras estés dentro del +sandbox, para que sepas fácilmente dónde estás. Si quieres, puedes elegir otro +color. + +![Ventana modal Create Sandbox](/img/create_sandbox_modal.webp) + +Cuando estés listo, haz clic en **Create Sandbox**. Entrarás automáticamente al +sandbox. + +Al crear el sandbox, se copian los miembros del proyecto principal: quien lo +crea pasa a ser `owner` del sandbox, los propietarios del proyecto principal +pasan a `admin` y los demás miembros mantienen su rol original. A partir de ahí, +el sandbox gestiona sus propios miembros: agregar o quitar a alguien en el +proyecto principal no afecta a los sandboxes que ya existen debajo. + +Todos los triggers de workflow del nuevo sandbox empiezan **deshabilitados**. +Así se evita duplicar runs de producción desde el momento en que existe el +sandbox. Puedes volver a habilitar cualquier trigger que quieras probar desde la +página del workflow en el sandbox. + +Después de crearlo, la acción Edit de la tarjeta de un sandbox te permite +renombrarlo y cambiar su color. Todo lo demás (workflows, credenciales, +miembros, entorno y el resto de la configuración) se gestiona desde dentro del +propio sandbox. + +### Límites {#limits} + +Hay dos límites prácticos para crear sandboxes: + +- **Cantidad de sandboxes activos.** Tu suscripción incluye un tope de sandboxes + activos (no programados para eliminarse) que puedes tener. Cuando llegas al + tope, el botón **Create Sandbox** se deshabilita y muestra un mensaje + emergente que explica por qué. Los sandboxes programados para eliminarse no + cuentan, así que puedes liberar un lugar eliminando uno que ya no necesites. +- **Profundidad de anidamiento.** Un sandbox puede tener a su vez sandboxes + debajo, y así sucesivamente, hasta una profundidad configurable (5 por + defecto). Cuando el proyecto principal llega al tope, el botón **Create + Sandbox** de su página de sandboxes se deshabilita y muestra el mensaje + emergente "Maximum sandbox nesting depth reached". + +## Quién puede hacer qué {#who-can-do-what} + +El acceso a los sandboxes funciona igual que en los proyectos normales: lo que +puedes ver y hacer depende de tu rol en el proyecto sobre el que actúas. El enum +`User.role` (`:user` / `:superuser`) es un tipo de usuario para las pantallas +globales de gestión de usuarios, no un rol de proyecto. + +| Acción | Rol necesario | +| ----------------------------------- | ---------------------------------------------------------------------------------------------- | +| Ver un sandbox | Fila directa en `project_users`, o usuario de soporte con `allow_support_access` en el sandbox | +| Abrir un sandbox | Igual que el anterior | +| Crear un sandbox | `editor`, `admin` u `owner` en el proyecto principal | +| Editar o eliminar un sandbox | `admin` u `owner` en el propio sandbox, o `admin`/`owner` en la raíz del espacio de trabajo | +| Hacer merge de un sandbox | `admin` u `owner` en el origen, y `editor` o superior en el destino del merge | +| Cancelar una eliminación programada | Igual que para editar o eliminar | + +El selector global de proyectos oculta los proyectos que no puedes ver. + +## Ver un sandbox {#viewing-a-sandbox} + +Puedes ver todos los sandboxes de un proyecto en la página **Sandboxes** del +menú lateral. Desde ahí, haz clic en el nombre de un sandbox para entrar. +También puedes entrar a un sandbox desde el selector global de proyectos +(Ctrl/Cmd+P). + +Cuando estás dentro de un sandbox, el selector de proyectos de la ruta de +navegación muestra el nombre del sandbox y toma el color elegido para él, así +sabes fácilmente qué versión de tu proyecto estás viendo. + +![Ruta de navegación dentro de un sandbox](/img/sandbox_breadcrumb.webp) + +Cada sandbox tiene sus propios workflows, colecciones, historial y +configuración, aislados. Al recorrer las páginas, notarás que no aparecen los +datos de tu proyecto original. Esto se debe a que tu sandbox es un clon +independiente del proyecto original. + +Cada pestaña de la página Settings muestra un aviso que explica cómo se +relacionan sus cambios con el proyecto principal: + +- **Sandbox Identity, Collaboration, Sync to GitHub, Data Storage, History + Exports**: los cambios son privados del sandbox y no se sincronizan al hacer + merge. +- **Credentials**: los cambios se sincronizan con el proyecto principal al hacer + merge. +- **Security**: de solo lectura en el sandbox; se gestiona en el proyecto + principal. +- **Webhook Security**: lo gestiona el proyecto principal. Los métodos se + comparten y se aplican a los triggers webhook del sandbox, pero solo se pueden + crear, editar o eliminar desde el proyecto principal. + +![Aviso de configuración del sandbox en la pestaña Credentials](/img/sandbox_settings_banner.webp) + +## Entornos {#environments} + +Los entornos te permiten ejecutar un workflow con un conjunto especial de +valores de credenciales, separado del de tu proyecto principal. Así puedes usar +servidores, modos y bases de datos de desarrollo mientras trabajas en tu +sandbox, sin interferir con los servicios de producción en vivo. + +El entorno es solo una etiqueta, y cada credencial que usa tu workflow tiene un +conjunto de valores asociados a esa etiqueta. Por ejemplo, al conectarte a +DHIS2, tu credencial principal tendrá datos de inicio de sesión privados, pero +tu entorno `dev` podría usar el sandbox público y tener un nombre de usuario y +una contraseña diferentes. + +Por defecto, todos los sandboxes reciben el entorno `dev`. Puedes cambiarlo +desde la página Settings. + +Todos los entornos se guardan cifrados y de forma segura en nuestra base de +datos, así que es totalmente seguro duplicar credenciales de producción en +varios entornos. + +Para cada credencial que use tu workflow, debes asegurarte de que haya un valor +configurado para el entorno de tu sandbox. Si no configuras tus credenciales, el +workflow fallará con instrucciones claras para corregirlo. + +## Hacer merge de sandboxes {#merging-sandboxes} + +Cuando termines de hacer cambios en tus workflows, es hora de hacer merge de +esos cambios de vuelta en el proyecto principal (o en cualquier otro proyecto en +el que puedas escribir). + +Ve a la página **Sandboxes**, busca el sandbox en la lista y haz clic en el +ícono **Merge** de la derecha. Se te pedirá que elijas el proyecto de destino +del merge. Normalmente querrás hacer merge en el proyecto principal original, +que viene seleccionado por defecto. + +También puedes elegir qué workflows incluir en el merge. Esto ayuda a reducir +los conflictos con cambios en el proyecto de base y a entender las consecuencias +del merge. + +Al hacer merge, reemplazamos el contenido de los workflows del proyecto de +destino con el de tu sandbox. Si renombras un workflow, parecerá que se eliminó +de la base y se agregó un workflow nuevo. + +El merge de las colecciones es por relación, no por datos. Una colección que +solo existe en el sandbox se crea vacía en el destino. Una colección que solo +existe en el destino se elimina (junto con sus elementos). Una colección con el +mismo nombre en ambos lados no se toca: no se copian los elementos de ninguno de +los dos. + +Para hacer merge de un sandbox, debes ser `admin` u `owner` del **origen** (el +sandbox desde el que haces merge); si no, el botón Merge está deshabilitado. +También necesitas el rol `editor` o superior en el proyecto de destino; si no, +se rechaza el merge. + +Después del merge, el sandbox de origen queda **programado para eliminarse** +tras el período de gracia configurado. Pasa a la sección "Scheduled for +deletion" de la lista de sandboxes, donde puedes restaurarlo durante ese plazo +si cambias de opinión. Los entornos y las credenciales asociados al proyecto no +se ven afectados. + +Si el sandbox del que haces merge tiene sus propios sandboxes debajo, también se +programan para eliminarse. La ventana modal de confirmación del merge te muestra +cuántos descendientes se retirarán junto con el origen. + +![Ventana modal para hacer merge de un sandbox](/img/merge_sandbox_modal.webp) + +## Conflictos {#conflicts} + +Si alguna vez trabajaste con un sistema de control de versiones de código, como +git o Subversion, ya conocerás la idea de los conflictos. + +Puede haber un conflicto cuando intentas hacer merge de un sandbox en un +proyecto de destino y el destino cambió desde que se creó el sandbox. Por +ejemplo, cambias el adaptor de un step en el sandbox de `common` a `http`, +mientras que un colega cambia el adaptor de ese mismo step en el proyecto +principal a `salesforce`. No hay forma automática de combinar esos cambios. + +Para ayudarte a ver qué pasa, la ventana modal de confirmación del merge marca +cada workflow con una de cuatro etiquetas: + +- **Changed**: el workflow se modificó en el sandbox y se reemplazará la copia + del destino. +- **Diverged**: el workflow se modificó en el destino después de crear el + sandbox. Si lo incluyes en el merge, se sobrescribirán esos cambios del + destino. Los workflows divergentes se marcan con un ícono de advertencia + ámbar. +- **New**: el workflow no existe en el destino y se creará. +- **Deleted in sandbox**: el workflow se eliminó en el sandbox. Si lo incluyes, + se elimina del destino. + +Tú eliges qué workflows incluir. Seleccionar uno **Diverged** es decidir de +forma explícita sobrescribir la versión del destino con la del sandbox. No hay +una herramienta para resolver conflictos en la aplicación: o aceptas la versión +del sandbox o dejas el workflow fuera. + +Puedes usar git y la CLI para resolver localmente los conflictos de los +workflows divergentes: consulta +[Resolver conflictos de merge](/build-for-developers/cli-sync.md#resolving-merge-conflicts). + +Si necesitas combinar cambios de ambos lados, descarga los dos proyectos con la +CLI, resuelve las diferencias localmente y vuelve a enviar el resultado. + +## Restaurar un sandbox eliminado {#restoring-a-deleted-sandbox} + +Cuando se elimina un sandbox, ya sea de forma explícita o como parte de un +merge, no se borra de inmediato. El sandbox (y cualquier sandbox que tenga +debajo) queda programado para eliminarse, con todos los triggers de sus +workflows deshabilitados, y un worker lo borra definitivamente cuando termina el +período de gracia configurado. + +Durante ese plazo, el sandbox aparece en la sección "Scheduled for deletion", al +final de la lista de sandboxes. Cualquier persona con permisos de `admin` u +`owner` en el sandbox (o en la raíz del espacio de trabajo) puede hacer clic en +**Restore** para cancelar la eliminación programada. Al restaurarlo, se +reactivan el sandbox y sus descendientes; los triggers siguen deshabilitados y +hay que volver a habilitarlos a mano. + +![Sección Scheduled for deletion](/img/scheduled_for_deletion.webp) + +Cuando termina el período de gracia y se ejecuta el worker de purga, el sandbox +desaparece para siempre. + +## Editar sandboxes localmente {#editing-sandboxes-locally} + +Los sandboxes son totalmente compatibles con la CLI. + +Usa `openfn project pull` para descargar un sandbox localmente y +`openfn project push` para enviar los cambios de vuelta al sandbox en la +aplicación. + +Puedes usar `openfn project merge` para hacer merge de dos proyectos locales y +luego `openfn project deploy` para sincronizarlos con la aplicación. + +Si trabajas con varios sandboxes en un mismo espacio de trabajo, hay dos cosas +que ayudan: + +- `openfn project fetch` asigna alias a los sandboxes automáticamente. Cuando + descargas un sandbox sin indicar `--alias`, la CLI usa el id del sandbox como + alias, para que varios sandboxes puedan convivir en el mismo espacio de + trabajo sin chocar con el proyecto principal ni entre sí. +- `openfn project checkout ` cambia el proyecto activo del espacio de + trabajo. Úsalo para pasar de un sandbox a otro entre los que ya descargaste. + +## Buenas prácticas {#best-practices} + +Los sandboxes se corresponden muy bien con las ramas de git, y las formas de +trabajo que mejor funcionan los tratan de la misma manera. + +**Crea un sandbox por cada tarea.** Dale a cada funcionalidad nueva, corrección +o issue su propio sandbox, en lugar de compartir un único sandbox `testing` de +larga duración entre varias personas y varios cambios. Los sandboxes pequeños y +de corta duración son más fáciles de revisar y es mucho menos probable que +generen conflictos al hacer merge. + +**Apila sandboxes cuando un trabajo depende de otro anterior.** Si un sandbox +tiene que existir durante un tiempo (por ejemplo, mientras espera una revisión o +una ventana de lanzamiento) y quieres empezar algo que se apoya en él, crea un +sandbox de ese sandbox en lugar de hacer merge antes de tiempo solo para +desbloquearte. Los sandboxes se pueden anidar hasta la profundidad configurada. +Haz lo mismo en git: crea la rama del trabajo dependiente a partir de la rama de +la funcionalidad, no de main. + +**Evita trabajar directamente en el proyecto principal.** Los cambios que hagas +ahí están en vivo y aparecen como workflow divergente en el merge de todos los +demás sandboxes. Empieza en un sandbox y haz merge cuando el cambio esté listo. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-design-intro.md b/i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-design-intro.md new file mode 100644 index 000000000000..76f34dc9aba4 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-design-intro.md @@ -0,0 +1,90 @@ +--- +title: Diseña tu step +translation_source_hash: 00a872e0912678e679517b3b01dfd0e645c71d84 +translation_review_status: machine +--- + +Antes de configurar un step de un workflow, tienes que diseñar el workflow +completo y pensar qué tareas, actividades o lógica de negocio concretas +ejecutará cada step. Sigue leyendo para ver un breve resumen. + +:::tip + +Consulta la documentación de [diseño de workflows](/design/design-overview.md) +para ver más detalles sobre el diseño de soluciones y enlaces a plantillas. + +::: + +En resumen, para diseñar un step de un workflow, tienes que seguir la lista de +acciones de abajo y considerar resumir las especificaciones de tu diseño en un +[diagrama del workflow](/design/design-workflow.md). + +![Ejemplo de workflow](/img/example-workflow-state.webp) + +## 1. Determina tus entradas y salidas {#1-determine-your-inputsoutputs} + +1. ¿Cuál es la entrada de este step del workflow? Piensa en cuál es el state + inicial, o los datos, _antes_ de que empiece el step. +2. ¿Cuál es la salida que quieres (es decir, el state final, o los datos, + _después_ de que se ejecute el step)? Piensa en qué quieres enviar a la + aplicación de destino o pasar al siguiente step del workflow. + +## 2. Mapea tus elementos de datos {#2-map-your-data-elements} + +[Consulta esta página](/design/mapping-specs.md) para ver una guía detallada +sobre cómo mapear los elementos de datos, o "diccionarios de datos", entre tus +aplicaciones de origen y de destino. Para empezar: + +1. Exporta los metadatos (o el "formulario", la "lista de campos" o los + "elementos de datos") de tu aplicación de origen (entrada) y de tu aplicación + de destino (salida). +2. Pega los metadatos en una hoja de cálculo de Excel para crear una hoja de + mapeo: + +![Ejemplo de hoja de mapeo](/img/data-element-mapping.webp) + +3. Mapea los elementos de datos de origen y de destino, y define reglas de + limpieza y transformación de los datos. Piensa en lo siguiente: + +- ¿Cómo se deberían traducir los datos recolectados al modelo de datos de tu + sistema de destino? +- ¿Tu sistema de destino tiene requisitos de entrada o de validación de datos? +- ¿Hay que transformar o limpiar los datos para cumplir con las respuestas + anteriores? + +## 3. Define tus métodos (GET, POST...) u operaciones (insert, update, upsert...) {#3-define-your-methods-get-post-andor-operations-insert-update-upsert} + +1. Determina qué identificadores únicos existentes puedes usar para tus datos, o + créalos. Los identificadores únicos sirven para insertar y actualizar + registros concretos de tus datos (por ejemplo, uuid, form_id, patient_id, + etc.). +2. Determina qué métodos HTTP (por ejemplo, GET, POST, PUT) u operaciones de + base de datos (por ejemplo, insert, update, delete) quieres hacer en la + aplicación de destino +3. Revisa las funciones auxiliares del adaptor. + - Ejemplo de [language-postgresql](/adaptors/packages/postgresql-docs) + - `insert(...)`, `insertMany(...)` + - `update(...)`, `updateMany(...)` + - `upsert(...)`, `upsertMany(...)` → actualiza el registro si existe o lo + inserta si no existe; hace referencia a un ID externo + - Ejemplo de [language-dhis2](/adaptors/packages/dhis2-docs) con Tracked + Entity Instances (TEI) + - `updateTEI(...)` + - `upsertTEI(...)` + +Mira el siguiente ejemplo de `Job expression` para un step que hace un "upsert" +(actualiza o inserta) de registros en una base de datos SQL. + +```js +upsert('mainDataTable', 'AnswerId', { + AnswerId: dataValue('\_id'), //external Id for upsert + column: dataValue('firstQuestion)'), + LastUpdate: new Date().toISOString(), + Participant: dataValue('participant'), + Surveyor: dataValue('surveyor'), + ... +}); +``` + +Consulta la [guía para escribir jobs](/jobs/job-writing-guide.md) para más +información. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-editor.md b/i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-editor.md new file mode 100644 index 000000000000..bce0b793481c --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/steps/step-editor.md @@ -0,0 +1,61 @@ +--- +title: Editar steps desde el Inspector +sidebar_label: Editar y probar steps +translation_source_hash: b452d22eaf108275e720a809f667abd2111ac105 +translation_review_status: machine +--- + +Esta página explica cómo editar y probar los steps de tu workflow con la +interfaz del Inspector. + +:::tip + +Si escribes jobs en la aplicación de la plataforma (Lightning), puedes usar el +[AI Assistant](/build/ai-assistant.md) para que te ayude. Lo encontrarás en el +Inspector. + +::: + +## Editar y probar steps desde el Inspector {#edit--test-steps-via-the-inspector} + +Usa la interfaz del `Inspector` de la plataforma para crear, editar y probar +steps. (Si creaste tu workflow localmente con la CLI, puedes editar tus jobs en +la aplicación desde esta interfaz). + +Para acceder a esta interfaz: + +1. Abre un workflow +2. Selecciona el step que quieres editar o probar +3. Haz clic en el botón de código `` del panel de configuración + +Para saber más sobre cómo escribir lógica de negocio personalizada y reglas de +transformación de datos en el `Editor`, consulta la documentación sobre +[cómo escribir jobs](/jobs/job-writing-guide.md) y mira el video de abajo. + + + +## Ejecutar y probar steps {#run--test-steps} + +Cuando ejecutas steps para probar la configuración, cada run tiene un state +inicial (que puede contener un `Input`) y da como resultado un state final que +incluye `Logs` y un `Output`. + +- `Input`: datos (JSON) que un step usa como entrada inicial en su run. Puede + haber una entrada para una work order y para cada step de un run, aunque + cualquiera de los dos puede existir sin entrada. +- `Output`: datos (JSON) que se crean como salida de la ejecución de un step. + Puede haber una salida para una work order y para cada job de un run, y suele + contener los datos enviados a la aplicación de destino. +- `Logs`: un registro que genera el motor de ejecución de workflows con el + detalle de las actividades realizadas al ejecutar un workflow o un step. + +Consulta la [documentación sobre cómo escribir jobs](/jobs/job-writing-guide.md) +para saber más sobre cómo escribir lógica personalizada, y +[este artículo](/jobs/state.md) para saber más sobre el concepto de "state" al +escribir jobs y crear workflows de OpenFn. + +## Atajos de teclado {#keyboard-shortcuts} + +Desde el Inspector puedes hacer algunas acciones comunes (por ejemplo, guardar, +ejecutar o sincronizar con GitHub) con el teclado. Consulta la lista completa de +atajos de teclado [aquí](/keyboard-shortcuts.md). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/steps/steps.md b/i18n/es/docusaurus-plugin-content-docs/current/build/steps/steps.md new file mode 100644 index 000000000000..ee5a5bbd18d4 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/steps/steps.md @@ -0,0 +1,156 @@ +--- +title: Configurar steps +translation_source_hash: 157984301b15b94a4df45d57fcf4b28494fa4e69 +translation_review_status: machine +--- + +Un step es una tarea o actividad concreta dentro de un workflow. Cada step está +vinculado a un [adaptor](/adaptors/) y contiene la lógica de negocio para hacer +una tarea u operación concreta en esa aplicación de destino. + +:::note + +En OpenFn V1 no existía el concepto de `Workflow Steps`: se llamaban `Jobs`. En +V2, los `Jobs` se definen como _las expresiones de job o los scripts que definen +la lógica de negocio y las reglas de transformación de cada uno de los `Steps`_. + +::: + +## Crear o editar un step {#create-or-edit-a-step} + +En el Canvas del workflow, haz clic en el ícono de más `+` para crear un step +_nuevo_, o haz clic en un step existente para ver o configurar sus componentes +principales. + +## Configurar el step {#configure-the-step} + +Para configurar bien un step, tienes que entender su anatomía básica. + +![Anatomía de un step](/img/anatomy_of_step.webp) + +Un step incluye estos componentes principales: + +- `Name`: un nombre legible que describe el step y su propósito. +- `Adaptor`: el [adaptor](/adaptors/) seleccionado, que aporta la funcionalidad + específica de la aplicación para este step (por ejemplo, `dhis2` o + `commcare`). +- `Adaptor Version`: la versión del adaptor seleccionado, que determina qué + endpoints de la API y qué funciones del adaptor están disponibles. Consulta la + sección [Elige una versión del adaptor](#3-choose-an-adaptor-version) más + abajo para saber más. +- `Credentials`: la credencial que se usa para autorizar las conexiones con la + aplicación de destino de este step. +- `Job`: el código personalizado que define la lógica de negocio o la secuencia + de operaciones que se ejecutan en la aplicación conectada, o ambas. + +:::tip Escribir jobs + +Escribir jobs para agregar lógica personalizada con reglas de negocio o de +transformación de datos suele requerir conocimientos básicos de JavaScript. +Consulta la [documentación sobre cómo escribir jobs](/jobs/job-writing-guide.md) +para ver una descripción detallada, y los +[ejemplos de la biblioteca](/adaptors/library) para ver código de muestra. + +::: + +## 1. Ponle nombre a tu step {#1-name-your-step} + +Primero, dale a tu step un `Name` que describa su propósito (por ejemplo, +`create patient`, `map form data`). + +## 2. Elige un adaptor {#2-choose-an-adaptor} + +Después, selecciona un `Adaptor` para definir con qué aplicación se conectará tu +step. + +:::tip + +Cada step solo puede tener 1 adaptor. Si quieres conectarte con 2 aplicaciones +distintas, deberías crear 2 steps distintos. + +::: + +Tenemos una sección completa sobre cómo crear [adaptors](/adaptors) nuevos, pero +lo más importante que debes saber al escribir un step es que tienes que elegir +un **adaptor** y una **versión del adaptor**. + +Todo lo que se explica más abajo sobre funciones auxiliares como `create` o +`findPatient` requiere entender un poco los adaptors. Cuando ejecutas un step, +usas una capa de funcionalidad que se construyó para conectarse con una API, un +tipo de API o una base de datos concretos. + +Por ejemplo, `create` significa una cosa en el adaptor `salesforce` y otra +completamente distinta en `dhis2`. Por eso, antes de empezar a escribir un step, +tienes que decidir con qué [adaptor](/adaptors/) vas a trabajar. + +### 3. Elige una versión del adaptor {#3-choose-an-adaptor-version} + +Elige la versión del adaptor que quieres usar. Te recomendamos seleccionar la +última versión disponible, salvo que quieras usar una versión anterior que sea +compatible con una versión anterior de la API con la que te conectas. Consulta +la [documentación de los adaptors](/adaptors) para ver los detalles de cada uno. + +Los adaptors cambian con el tiempo. Son de código abierto y fomentamos todas las +contribuciones posibles: publicamos versiones nuevas para usarlas en OpenFn.org +en cuanto pasan nuestras revisiones de seguridad. Puede que se agreguen +funcionalidades nuevas y se corrijan errores, pero, para asegurarte de que una +integración existente no se rompa, te recomendamos seleccionar una versión +concreta (en lugar de usar la funcionalidad de "actualización automática") +cuando elijas un adaptor. La versión publicada más alta es la opción +predeterminada. + +:::tip + +Las _primeras 4 líneas_ del log de cualquier run en OpenFn te dicen qué adaptor +estás ejecutando (además de la versión del worker, del engine y de Node.js). +Esto es muy importante, sobre todo si intentas solucionar problemas de steps en +distintos entornos (como tu propia terminal, app.openfn.org, etc.). + +::: + +Fíjate bien en qué `version` usas para escribir un step. Observa los siguientes +logs de un run: + +```sh +Versions for run f470a3da-8b90-480e-a94f-6dd982c91afe: + ▸ node.js 18.19.0 + ▸ worker 0.5.0 + ▸ engine 0.2.6 + ▸ @openfn/language-primero 2.9.1 +...more logs here... +``` + +#### Gestionar las versiones de los adaptors {#managing-adaptor-versions} + +Aunque actualizar puede ser útil como parte del mantenimiento habitual, estas +actualizaciones se deberían probar con cuidado. Lo más común es que los clientes +actualicen a una versión nueva del adaptor de un step existente cuando ya están +haciendo cambios en ese step por motivos de negocio. Algunos cambios de negocio +pueden incluso _requerir_ actualizar la versión para usar una funcionalidad +nueva del adaptor. Aunque esos cambios no requieran una actualización, si el +equipo técnico tiene que dedicar tiempo de todos modos a probar cambios en un +step, puede ser el momento ideal para probar también una actualización de la +versión del adaptor. + +Los adaptors siguen [SEMVER](https://semver.org/), así que puedes estar +razonablemente seguro de que actualizar de `x.1.z` a `x.2.z` no hará que falle +el código existente de un step, pero actualizar de `3.y.z` a `4.y.z` sí podría: +en SEMVER, las actualizaciones _mayores_ (las que cambian el primer número de la +versión `x.y.z`) tienen cambios "incompatibles" o "no retrocompatibles". + +:::tip + +Al configurar un step, puedes seleccionar una `Adaptor Version` concreta para +fijar la versión de tu step. Si eso es lo que quieres, y para evitar el riesgo +de actualizaciones accidentales en workflows en producción, no selecciones +`latest` como versión del adaptor. + +::: + +### 4. Escribe un job para la lógica de negocio personalizada o las reglas de transformación de datos {#4-write-a-job-for-custom-business-logic-or-data-transformation-rules} + +Haz clic en el botón de código `` del panel de configuración para escribir o +editar una expresión de job que defina las "reglas" o las tareas concretas que +debe completar tu step. Consulta las páginas sobre +[el Inspector](/build/steps/step-editor.md) y sobre +[cómo escribir jobs](/jobs/job-writing-guide.md) para saber más. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md b/i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md new file mode 100644 index 000000000000..c011fb328992 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/triggers.md @@ -0,0 +1,276 @@ +--- +title: Triggers +translation_source_hash: 151798ebc62b4d1787b5ce6d9d182260779897e3 +translation_review_status: machine +--- + +Los triggers permiten iniciar la ejecución de workflows automáticamente. Hay dos +tipos: triggers cron y triggers de eventos webhook. + +## Triggers de eventos webhook {#webhook-event-triggers} + +Los **triggers de eventos webhook** escuchan solicitudes HTTP entrantes +(mensajes de otros sistemas) y permiten una automatización en tiempo real, +basada en eventos. + +Estos triggers se disparan cuando se "empujan" datos a OpenFn (es decir, cuando +se envía una solicitud HTTP "POST" a la URL asignada a tu trigger). + +La solicitud HTTP que dispara el trigger puede llegar desde un webhook de una +aplicación externa, desde otro workflow de OpenFn o enviarse manualmente (es +decir, con una solicitud de cURL). + +![Trigger webhook](/img/webhook_trigger.webp) + +Para aprender a agregar una capa extra de seguridad a tu trigger webhook con +autenticación, ve a la página +[Seguridad de los webhooks](/manage-projects/webhook-auth.md). + +Aprende cómo se construye el `state` inicial de un workflow a partir de un +trigger webhook [aquí](/jobs/state.md#webhook-triggered-runs). + +## **Respuestas del trigger webhook** {#webhook-trigger-responses} + +Cuando un webhook dispara un workflow, OpenFn puede responder al sistema que +hizo la llamada de una de dos formas, según cómo esté configurado el trigger. + +### **El modo asíncrono responde antes de empezar** {#async-mode-responds-before-start} + +De forma predeterminada, los workflows se ejecutan de forma **asíncrona**. + +OpenFn envía una respuesta HTTP **en cuanto recibe la solicitud del webhook**, +una vez creados la work order y el run. El sistema que hizo la llamada recibe +una confirmación rápida y el workflow se ejecuta en segundo plano. + +**Usa este modo cuando:** + +- El sistema que hace la llamada solo necesita confirmar que se recibió la + solicitud +- Quieres respuestas rápidas y poca dependencia entre los sistemas +- El sistema que hace la llamada no necesita el resultado del run del workflow + +**Respuesta:** + +- Código de estado: `200` +- Encabezados: + - `x-meta-work-order-id`: el ID de la work order creada + - `x-meta-run-id`: el ID del run creado +- Cuerpo: + ```json + { + "work_order_id": "abc123", + "run_id": "xyz456" + } + ``` + +### **El modo síncrono responde al terminar** {#sync-mode-responds-after-completion} + +Si quieres, los workflows también se pueden ejecutar de forma **síncrona**. + +OpenFn mantiene abierta la conexión HTTP y envía una respuesta **cuando termina +el run**, con el state final del run como cuerpo de la respuesta. El sistema que +hizo la llamada espera el resultado, a veces durante segundos o minutos. + +**Usa este modo cuando:** + +- El sistema que hace la llamada necesita el resultado del run del workflow +- Necesitas la salida final del workflow para decidir el siguiente paso en el + sistema que hace la llamada + +**Respuesta predeterminada:** + +- Código de estado: `201` (configurable; ver más abajo) +- Encabezados: + - `x-meta-work-order-id`: el ID de la work order + - `x-meta-run-id`: el ID del run +- Cuerpo: un objeto JSON con esta forma: + ```json + { + "data": { /* the final run state */ }, + "meta": { + "work_order_id": "abc123", + "run_id": "xyz456", + "state": "success", + "error_type": null, + "inserted_at": "2026-05-21T10:00:00Z", + "started_at": "2026-05-21T10:00:01Z", + "claimed_at": "2026-05-21T10:00:01Z", + "finished_at": "2026-05-21T10:00:05Z" + } + } + ``` + - `data`: el state final del run (o un mensaje de seguridad si falla, o un + cuerpo personalizado; ver más abajo) + - `meta`: los metadatos del run, con las marcas de tiempo de su ciclo de vida + y el `state` final (`"success"` o `"failed"`) + +:::note Política de seguridad para los runs fallidos + +Cuando un run falla, OpenFn devuelve un mensaje genérico en `data` en lugar del +state completo del run, para no filtrar datos sensibles. Aun así, puedes +devolver un cuerpo personalizado desde un run fallido con `webhookResponse` (ver +más abajo). + +::: + +#### Configurar códigos de estado personalizados {#configuring-custom-status-codes} + +En el modo síncrono puedes configurar códigos de estado HTTP personalizados en +el trigger (en **Options → Response Status** en la interfaz): + +- **Success Status Code**: se devuelve cuando el run termina con éxito (el valor + predeterminado es `201`) +- **Error Status Code**: se devuelve cuando el run falla (el valor + predeterminado es `201`) + +:::note Volver al modo asíncrono + +Si vuelves a cambiar un trigger al modo asíncrono, se borran los códigos de +estado de éxito o de error que hayas configurado, porque solo se aplican en el +modo síncrono. + +::: + +#### Personalizar la respuesta desde tu job {#customising-the-response-from-your-job} + +Para devolver un cuerpo o un código de estado personalizados a partir de valores +en tiempo de ejecución, define `webhookResponse` en el state, por ejemplo: + +```js +fn(state => ({ + ...state, + webhookResponse: { + status: 200, + body: { ack: true, id: state.data.id }, + }, +})); +``` + +Al final del run, se usa el valor de `state.webhookResponse` para enviar la +respuesta HTTP al sistema que hizo la llamada. Cambiar el valor durante el run +no afecta a la respuesta: solo cuenta el state final. + +Tanto `status` como `body` son **opcionales**: puedes incluir uno de los dos o +ambos: + +| Campo | Comportamiento cuando se define | +| -------- | -------------------------------------------------------------------- | +| `status` | Reemplaza el código de estado configurado para este run | +| `body` | Reemplaza el state final del run en `data` en el cuerpo de respuesta | +| ninguno | Usa el código de estado configurado y el state final del run | + +`webhookResponse.body` solo reemplaza la parte `data` de la respuesta: OpenFn +siempre incluye `meta`. Así que el ejemplo de arriba produce: + +```json +{ + "data": { "ack": true, "id": "..." }, + "meta": { "work_order_id": "...", "run_id": "...", "state": "success", ... } +} +``` + +:::note Valores mal formados + +Si `webhookResponse` no es un objeto JSON (por ejemplo, si es una cadena, un +número o un array), se ignora y se aplican el código de estado y el cuerpo +predeterminados del run. + +Si `webhookResponse.status` no es un número entero, o `webhookResponse.body` no +es un objeto JSON, el código de estado de la respuesta vuelve al predeterminado +del run (el código de éxito o de error configurado, o `201`) y `data` se +reemplaza por +`{ "message": "Run completed, but webhook_response was malformed: ..." }`. + +::: + +## Triggers cron {#cron-triggers} + +Los **triggers cron** ejecutan workflows según una programación cron, y sirven +para tareas repetitivas basadas en el tiempo (por ejemplo, sincronizar datos +financieros entre dos sistemas todos los días a las 8 a. m.). + +Estos triggers permiten "extraer" datos de los sistemas conectados. Puedes +elegir una programación estándar (por ejemplo, todos los días o todos los meses) +o definir una programación personalizada con expresiones cron. + +:::tip Ayuda con las expresiones cron + +Si todavía no conoces `cron`, la mejor forma de aprender es desde la interfaz de +OpenFn o en crontab.guru. + +::: + +Con los triggers cron, los workflows se pueden ejecutar con una frecuencia de +hasta una vez por minuto, o con la poca frecuencia que quieras, y se pueden +programar en fechas u horas muy concretas. + +### `state` de entrada para el siguiente run {#input-state-for-the-next-run} + +Cada vez que se ejecuta un workflow con trigger cron, _empieza_ con la salida +final del último run exitoso. Así puedes crear workflows que usen un +["cursor"](/jobs/using-cursors.md) para saber qué pasó la última vez que se +ejecutó el workflow. (Por ejemplo, para procesar solo los datos que cambiaron +desde ese último run). + +![Trigger cron](/img/cron_trigger.webp) + +De forma predeterminada, el state de entrada del siguiente run cron será el +state de salida final del run anterior, pero puedes configurarlo para que use el +state de salida de un step concreto de ese run anterior cambiando el "Cron Input +Source". + +Encontrarás más información sobre el `state` en los runs con trigger cron en la +documentación de +["State de entrada y de salida"](/jobs/state.md#cron-triggered-runs). + +### Controlar el tamaño del `state` en los workflows cron {#managing-the-size-of-state-for-cron-workflows} + +Como el state pasa de un run al siguiente en un workflow cron, si un step de tu +workflow agrega algo nuevo al state cada vez que se ejecuta, el state puede +crecer rápidamente hasta ser demasiado grande para manejarlo en la práctica. +Imagina que, cada vez que se ejecuta el job, una respuesta del servidor se +agrega a `state.references` con `array.push(...)`. OpenFn admite hasta 50 000 +bytes (medidos con `byte_size` de Erlang), aunque la mayoría de los +`final_state` miden entre 100 y 1000 bytes. + +Si tu `final_state` supera los 10 000 bytes, OpenFn envía un correo de +advertencia a los colaboradores del proyecto. Si supera los 50 000 bytes, tu run +termina con éxito igualmente, pero su `final_state` no se guarda, y la próxima +vez que se ejecute ese job heredará el state final anterior, sin actualizar. (Es +decir, el último state que medía < 50 000 bytes). + +### Una solución rápida para un state final demasiado grande {#a-quick-fix-for-final-state-bloat} + +Casi siempre, un `state` final demasiado grande se debe a un mal manejo de +`state.references` o `state.data`. Para solucionarlo, limpia tu `state` final +agregando y adaptando las siguientes líneas, _ya sea_ en el callback de la +operación de tu paquete de lenguaje (si lo permite) o en una operación `fn(...)` +después de tu última operación. + +```js +fn(state => { + state.custom = somethingIntentional; + state.data = {}; + state.references = []; + return state; +}); +``` + +## Triggers de Kafka {#kafka-triggers} + +Los triggers de Kafka se eliminaron en la **v2.18.2**. Ya no se puede iniciar un +workflow consumiendo mensajes de un clúster de Kafka. + +Los triggers de Kafka existentes se convirtieron en **triggers webhook +deshabilitados** en lugar de borrarse, así que los workflows a los que +pertenecían siguen intactos. Para que uno siga funcionando, configura el sistema +que envía los datos para que apunte a la URL del webhook del trigger y +habilítalo. + +:::caution Para instalaciones de OpenFn autoalojadas + +Las variables de entorno `KAFKA_*` ya no hacen nada. Si tienes +`KAFKA_TRIGGERS_ENABLED` activado, haz una copia de seguridad y desactívalo +antes de actualizar. Si todavía necesitas Kafka, quédate en la versión anterior. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/troubleshooting.md b/i18n/es/docusaurus-plugin-content-docs/current/build/troubleshooting.md new file mode 100644 index 000000000000..152a599bc16c --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/troubleshooting.md @@ -0,0 +1,143 @@ +--- +title: Solución de problemas en integraciones +sidebar_label: Solución de problemas +translation_source_hash: 82afbc334c9cd834780739572f9a016a72b38efa +translation_review_status: machine +--- + +Así que notaste que algo no anda del todo bien. Aquí tienes una lista de +preguntas, y de complicaciones, que pueden ayudarte a llegar al fondo del +asunto. + +:::tip + +Consulta la +[página de solución de problemas](/monitor-history/troubleshooting.md) de la +sección "Supervisar el historial" para ver consejos más específicos y errores +comunes. + +::: + + + +## La perspectiva de la implementación {#the-implementation-perspective} + +Primero, ten a mano esta lista de verificación rápida... responder estas +preguntas _en orden_ hará que dediques el menor tiempo posible a encontrar la +causa del problema, sea grande o pequeño. + +### 1. ¿Qué quieres? {#1-what-do-you-desire} + +Responder esto podría llevarte toda la vida, pero, en el contexto de la +depuración, puedes acotarlo un poco. De verdad no podemos avanzar hasta que +tengas claro lo que quieres. + +### 2. ¿Cómo lo estás pidiendo? {#2-how-are-you-asking-for-it} + +¡Muéstrame el issue, las especificaciones, el "requisito"! Asegurémonos de que +esté expresado con claridad y documentado. Si es así, ¡pasa a la pregunta 3! + +### 3. ¿Lo que pides va a producir el efecto que quieres? {#3-is-what-youre-asking-for-going-to-produce-the-effect-you-desire} + +Esta es difícil y puede requerir al equipo de ingeniería. (De hecho, suele ser +en este momento cuando se llama a ingeniería. Hay un "bug", y antes de mirar +cualquier código tenemos que averiguar si lo que se pide, la especificación, +realmente va a producir los resultados deseados). + +### 4. ¿La expresión implementa lo que pides? {#4-does-the-expression-implement-what-youre-asking-for} + +¿Estamos _seguros_ de que la especificación va a producir el efecto que +queremos? Bien, perfecto... ahora veamos la expresión del job. ¿La expresión del +job implementa la especificación? ¿Cómo puedes demostrar (con logs, aserciones, +etc.) que lo hace? No avances hasta estar seguro de esto, o seguro de que _no +puede_ hacerlo, dado el adaptor que usas. + +:::info Control de tiempo + +Nota: un cambio en la expresión del job puede llevar apenas un par de minutos. + +::: + +### 5. ¿El adaptor permite la implementación de la expresión? {#5-does-the-adaptor-supportenable-the-implementation-in-the-expression} + +Bien, si estás seguro de que la expresión hace todo lo que puede con la +especificación... ¡quizás hay un bug en el adaptor! Puede que algo en cómo se +implementó esa función auxiliar no haga lo que pretendía el autor del adaptor, y +eso podría estar produciendo el "bug". + +Si empiezas a trabajar en el adaptor, _ya_ deberías haber reducido el problema a +un **_PROBLEMA GENERAL_**, dejando de lado todos los detalles específicos de +esta implementación. Estás empezando a cambiar la forma en que este adaptor +interactúa con la API de destino. Tienes a mano la documentación de la API y +estás enviando solicitudes con cURL directamente a distintos endpoints, +configurando pruebas en el adaptor, etc. + +:::info Control de tiempo + +Un cambio en el adaptor puede llevar una hora, o quizás unas cuantas. Hablamos +del orden de un día si los cambios son grandes y cuentas el tiempo necesario +para desplegar versiones nuevas. + +::: + +### 6. ¿La API de destino permite la implementación del adaptor? {#6-does-the-target-api-supportenable-the-implementation-in-the-adaptor} + +Uf... si llegaste hasta aquí, estás en terreno "grande y serio". ¡Avanza con +cuidado! Supongo que encontraste muchos hilos de Stack Overflow que describen el +problema que tienes. Lo que estás diciendo es que, _a pesar de_ la documentación +de la API que usamos para construir este adaptor, hay algo distinto en cómo se +comporta realmente la API. + +¿Quizás hay una versión nueva de la API con un cambio incompatible? + +¿Quizás hay un bug en el sistema de destino? + +En cualquier caso, cuando llegas a este nivel estás dedicando MUCHO tiempo y te +estás involucrando con la comunidad open source en general. Deberías publicar en +al menos un foro antes de terminar el día. + +:::info Control de tiempo + +Escribir un adaptor nuevo para una versión nueva de una API, o corregir un bug +en el sistema de otro desarrollador mediante una pull request... esto lleva +semanas y meses y, peor aún, los plazos suelen estar fuera de nuestro control. + +::: + +## La perspectiva del producto {#the-product-perspective} + +Para complicar las cosas _(¡acepta la complejidad!)_, cuando me pongo el +sombrero de producto, invierto la pirámide. Aunque un problema se pueda resolver +en 15 minutos escribiendo una línea nueva en la `expression` (consulta la +pregunta 4), ¿es un problema generalizable? ¿Podría ahorrarles esos 15 minutos a +_futuros implementadores_ haciendo un cambio en el adaptor (consulta la +pregunta 5) que ofrezca esta corrección o funcionalidad "de serie"? + +Mejor aún... ¿podría hacer algún cambio en la plataforma OpenFn (o en Primero, +CommCare o DHIS2) que permitiera adaptors más fáciles o mejores y resolviera +este problema con clics, no con código? + +:::tip + +¿Recuerdas esos jobs que escribíamos que no hacían nada (simplemente devolvían +el state) si se cumplía una condición? Pues bien, con exactamente este enfoque +incorporamos a OpenFn una funcionalidad de "filtro de exclusión" que permite a +un usuario omitir ciertos mensajes entrantes según unos criterios, en lugar de +tener que evaluar esos mensajes en el job. + +Llevó mucho más trabajo que escribir ese único bloque `fn(...)` al principio del +job de un solo cliente, pero ahora le ahorra a _todo el mundo_ escribir esa +línea en el futuro. + +::: + +## Encontrar el equilibrio, al final {#find-balance-in-the-end} + +Estas preguntas siempre me dan vueltas en la cabeza e intento sopesar esta +perspectiva del producto frente a la perspectiva de la implementación. Al final, +todo es cuestión de equilibrio (nada sorprendente) en cómo _resolvemos_ estos +problemas, pero seguir la perspectiva de la implementación en cómo abordas, +entiendes, depuras y estimas pondrá más información sobre la mesa más rápido y +permitirá una mejor conversación del tipo "Bien, ¿cómo deberíamos resolver esto +dadas las limitaciones cronológicas y comerciales actuales?" entre el equipo de +implementación y el equipo de ingeniería. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/workflow-snapshots.md b/i18n/es/docusaurus-plugin-content-docs/current/build/workflow-snapshots.md new file mode 100644 index 000000000000..98b2923bd56b --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/workflow-snapshots.md @@ -0,0 +1,103 @@ +--- +title: Snapshots de workflows +sidebar_label: Snapshots de workflows +slug: /workflow-snapshots +translation_source_hash: 8a952794f200dfcaec11953aa81686bf7244d82c +translation_review_status: machine +--- + +Los snapshots de workflows capturan y guardan un estado o versión de un workflow +(la combinación de entrada, configuración del workflow y código del job) en el +momento concreto en que el workflow se actualizó o se ejecutó. Los snapshots +ayudan a depurar, auditar y mejorar el rendimiento general de los workflows. + +### ¿Cuándo se crea un snapshot? {#when-is-a-snapshot-made} + +Los snapshots se crean de 2 formas: + +1. Cuando un usuario guarda cambios en su workflow, ya sea desde el Canvas o + desde el Inspector +2. Cuando se crea un run, ya sea al crear una nueva work order o al reintentar + un run + +### ¿Cómo puedo ver un snapshot? {#how-can-i-view-a-snapshot} + +Para ver un snapshot, ve a la página `History`. Expande una work order para ver +los runs que incluye. + +![Snapshot1](/img/snapshots1.webp) + +Desde la vista expandida del historial, hay dos formas de ver los snapshots: + +1. Inspeccionando un step del run +2. Desde la vista del run + +#### Ver un snapshot inspeccionando un step del run {#viewing-a-snapshot-by-inspecting-a-step-in-the-run} + +Haz clic en el ícono de inspeccionar junto al step que quieres ver. + +![Inspect](/img/inspect.webp) + +Se abrirá la [pantalla del Inspector](/build/steps/step-editor.md) para ese step +del run, con todos sus artefactos asociados: logs y datos de entrada y salida. +Verás que el Inspector está en modo de solo lectura y, si pasas el cursor sobre +el chip con el ID del snapshot del workflow, aparecerá un mensaje que dice "You +are viewing a snapshot of this workflow that was taken on …." + +![Snapshot2](/img/snapshots2.webp) + +Para ver el Canvas correspondiente a este snapshot, cierra la vista del +Inspector haciendo clic en la `X` de la esquina superior derecha de la página. +Se abrirá el Canvas asociado, con el step seleccionado, como se muestra a +continuación. + +![Snapshot3](/img/snapshots3.webp) + +Desde el Canvas, puedes inspeccionar cualquier step haciendo clic en él y +abriendo el Inspector para el run asociado al step y al snapshot. + +#### Ver un snapshot desde la vista del run {#viewing-a-snapshot-from-the-run-view} + +Desde la vista expandida del historial, haz clic en el ID del run para abrir la +vista del run. + +![Snapshot4](/img/snapshots4.webp) + +En esta vista, haz clic en el nombre del workflow (Simple Flow) para abrir el +Canvas del workflow para este snapshot. Igual que al ver un snapshot +inspeccionando un step, puedes hacer clic en el ícono de inspeccionar junto a +los steps para abrir el Inspector del step. + +### Editar un snapshot {#editing-a-snapshot} + +Los snapshots son de solo lectura y sirven como referencia del estado de un +workflow en el momento en que se guardó o se ejecutó un run. Como solo se puede +editar la versión más reciente, para editar el workflow puedes hacer clic en +`Switch to latest version` en el Canvas o usar el interruptor de la esquina +inferior derecha de la página del Inspector para cambiar a la versión más +reciente del workflow. + +Cuando cambias a la versión más reciente, la etiqueta con el ID del snapshot se +vuelve azul y su texto pasa a ser `latest`. + +![Snapshot5](/img/snapshots5.webp) + +![Snapshot6](/img/snapshots6.webp) + +### Reintentar un snapshot {#retrying-a-snapshot} + +Cuando reintentas un run con un snapshot, el reintento se ejecuta con la versión +más reciente del workflow y del código del job. No puedes reintentar un workflow +con un snapshot anterior, solo con la versión más reciente. + +### Snapshots y control de versiones {#snapshots-and-version-control} + +Como guardan un workflow con un conjunto concreto de configuración, datos de +entrada y código del job, los snapshots son sobre todo herramientas que ayudan a +los administradores a auditar y a manejar errores (por ejemplo, para saber por +qué un caso no se actualizó correctamente en una base de datos). + +OpenFn ofrece herramientas específicas de +[control de versiones](/manage-projects/link-to-gh.md) que te permiten a ti y a +tu equipo administrar los cambios en el código de los jobs para desarrollar, +depurar y revisar de forma más rápida y segura. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/workflows-api.md b/i18n/es/docusaurus-plugin-content-docs/current/build/workflows-api.md new file mode 100644 index 000000000000..28a4268f4be0 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/workflows-api.md @@ -0,0 +1,173 @@ +--- +title: API de workflows +sidebar_label: API de workflows +translation_source_hash: 770a1a41ea82bd8249b1dfe732d7b21a8710563c +translation_review_status: machine +--- + +La API de workflows te permite crear y modificar workflows mediante código. + +Puedes usar la API de workflows con el adaptor `http` o con curl. + +:::info Compatibilidad de versiones + +La API de workflows se introdujo en la versión 2.10.10, en enero de 2025 + +::: + +## Autenticación {#authentication} + +Todas las solicitudes deben estar autenticadas. + +La autenticación usa el encabezado Authorization con un token de acceso personal +(Personal Access Token, PAT) generado en la aplicación. + +Si usas el adaptor http, asigna tu PAT al `access_token` de la credencial. + +Si usas curl, agrega el bearer token (en el ejemplo de abajo, el token se toma +de una variable de entorno): + +``` +curl -H "Authorization: Bearer $OPENFN_PAT" https://app.openfn.org/api/projects//workflows +``` + +## API REST {#rest-api} + +La API de workflows tiene la siguiente estructura RESTful: + +- `GET /api/projects/:projectId/workflows`: obtiene la lista de workflows de un + proyecto. Devuelve un array de workflows. +- `GET /api/projects/:projectId/workflows/:workflowId`: obtiene un solo workflow + por su id. Devuelve un solo workflow. +- `PUT /api/projects/:projectId/workflows`: crea un workflow nuevo. Incluye el + JSON del workflow en el cuerpo. Devuelve el JSON del workflow actualizado. +- `PUT /api/projects/:projectId/workflows/:workflowId`: actualiza un workflow. + Reemplaza el workflow existente por el JSON del cuerpo. Devuelve el JSON del + workflow actualizado. +- `PATCH /api/projects/:projectId/workflows/:workflowId`: actualiza un workflow + en parte. El workflow existente se actualiza con el JSON del cuerpo. + +## Estructura de un workflow {#workflow-structure} + +Un workflow tiene la siguiente estructura: + +``` +{ + "name": "My Workflow", + "id": "a414cb3b-e387-4c4f-b8de-70d51f1160da", + "project_id": "79efba60-072a-4d4f-8d6c-22dfd3852176", + "edges": [ + { + "id": "759fe475-ed23-4914-8a6d-155968bc0aa1" + "condition_type": "always", + "enabled": true, + "source_job_id": null, + "source_trigger_id": "c79ce46c-ab0f-4f5b-bf2d-fed52aef2a41", + "target_job_id": "26304a1e-267b-4bc9-940f-171db1905885", + } + ], + "jobs": [ + { + "id": "26304a1e-267b-4bc9-940f-171db1905885", + "body": "/* job code goes here */", + "name": "my-job", + "adaptor": "@openfn/language-common@latest", + } + ], + "triggers": [ + { + "id": "c79ce46c-ab0f-4f5b-bf2d-fed52aef2a41", + "comment": null, + "custom_path": null, + "cron_expression": null, + "type": "webhook", + "enabled": true + } + ], +} +``` + +Al crear un workflow nuevo, el servidor genera UUIDs para el workflow y para +todos sus steps y edges. Al crear nodos y edges nuevos puedes usar cualquier +cadena como id, siempre que la uses de forma coherente. + +En una solicitud PUT o PATCH, a los steps y edges nuevos se les DEBEN asignar +UUIDs. Si usas el adaptor `http`, puedes usar `util.uuid()` para hacerlo (ver el +ejemplo de abajo). + +DEBES asegurarte de que todos los steps y triggers a los que hace referencia un +edge estén definidos dentro del mismo workflow. + +## Ejemplos con el adaptor HTTP {#http-adaptor-examples} + +Tienes que crear una credencial con `access_token` igual a tu token de acceso +personal (PAT) y `baseUrl` igual a tu instancia de OpenFn (por ejemplo, +`"https://app.openfn.org"`) + +Crear un workflow nuevo: + +```js +post(`/api/projects/${$.projectId}/workflows`, { + body: { + name: 'My Workflow', + edges: [ + { + source_trigger_id: 'trigger-1', + target_job_id: 'job-1', + condition_type: 'always', + }, + ], + jobs: [ + { + id: 'job-1', + name: 'My Job', + body: '/* job code goes here */', + adaptor: '@openfn/language-common@latest', + }, + ], + triggers: [ + { + id: 'trigger-1', + type: 'webhook', + enabled: true, + }, + ], + }, + headers: { 'content-type': 'application/json' }, +}); +``` + +El workflow resultante, con los UUIDs y los metadatos actualizados, se escribe +en `state.data.workflow`. + +Agregar un step y un edge nuevos a un workflow existente: + +```js +fn(state => { + const jobId = util.uuid(); + state.diff = { + edges: [ + { + id: util.uuid(), + source_job_id: 'c79ce46c-ab0f-4f5b-bf2d-fed52aef2a41', + target_job_id: jobId, + condition_type: 'always', + }, + ], + jobs: [ + { + id: jobId, + body: '/* job code goes here */', + adaptor: '@openfn/language-common@latest', + }, + ], + }; + return state; +}); +patch(`/api/projects/${$.projectId}/workflows/${$.workflowId}`, { + body: $.diff, + headers: { 'content-type': 'application/json' }, +}); +``` + +El workflow resultante se escribe en `state.data.workflow`. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/workflows.md b/i18n/es/docusaurus-plugin-content-docs/current/build/workflows.md new file mode 100644 index 000000000000..5a0b3011a3e3 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/workflows.md @@ -0,0 +1,214 @@ +--- +title: Workflows +sidebar_label: Workflows +translation_source_hash: 13857c3ec6be5f4b35e8adf71570bb4f22318f05 +translation_review_status: machine +--- + +Los **workflows** son procesos automatizados o conjuntos de instrucciones que +cumplen una tarea. En la configuración de OpenFn, un workflow está formado por +un trigger, steps y paths que definen la lógica de la automatización. Sigue +leyendo para aprender a configurar workflows. + +## Crear workflows {#create-workflows} + +Para crear un workflow nuevo en tu proyecto: + +1. Ve a la página **Workflows**. +2. Haz clic en el botón **Create new workflow**. +3. Dale a tu workflow un `Name` descriptivo (por ejemplo, `Register patients`, + `Refer cases`, `Monthly payroll`). +4. Elige tu [trigger](/build/triggers.md) +5. Edita tu primer [step](/build/steps/steps.md) +6. Si hace falta, modifica la [condición del path](/build/paths.md) para definir + _cuándo_ debe pasar el workflow al siguiente step. +7. Configura más steps según lo necesites + +Mira el video de presentación de abajo para aprender a crear un workflow. + + + +### Unir ramas y saltar steps {#merging-branches-and-skipping-steps} + +El editor de workflows permite unir ramas y saltar steps. Para unir dos o más +steps en uno solo, o para saltar algunos steps: + +1. Pasa el cursor sobre el step que quieres unir o desde el que quieres saltar +2. Verás un ícono de más +3. Haz clic en el ícono de más y arrastra para crear un path +4. Suelta el path nuevo sobre el step que quieras de tu workflow + + + +:::note No se admiten bucles + +Los workflows con bucles no se admiten, así que tienes que conectar los paths a +steps posteriores. Cuando uses paths que unen ramas o saltan steps, puedes usar +condiciones igual que con cualquier otro step. + +::: + +## Ejecutar workflows {#run-workflows} + +Los workflows se ejecutan automáticamente cuando están "habilitados", es decir, +cuando su trigger está activado. Un trigger webhook ejecuta tu workflow cada vez +que llega una solicitud a la URL de ese trigger, y un trigger cron ejecuta un +workflow cada vez que su programación cron coincide con la hora actual. + +:::info + +Ten en cuenta que los workflows están deshabilitados de forma predeterminada. +Cuando quieras que tu workflow empiece a ejecutarse, tienes que habilitarlo +manualmente. + +::: + +### Habilitar o deshabilitar un workflow {#enabling-or-disabling-a-workflow} + +Hay dos formas de deshabilitar o habilitar un workflow en tu proyecto de OpenFn: + +1. con el interruptor de estado del workflow +2. con el trigger del workflow + +#### Con el interruptor de estado del workflow {#via-the-workflow-state-toggle} + +Puedes habilitar o deshabilitar tu workflow con el interruptor que está en su +fila de la lista de workflows del proyecto, o con el interruptor de la barra de +navegación del Canvas del workflow. + +La captura de pantalla de abajo muestra un workflow habilitado en la lista de +workflows. + +![Desde la lista de workflows](/img/workflow_list_toggle.webp) + +La captura de pantalla de abajo muestra un workflow deshabilitado en el Canvas +del workflow. + +![Desde el Canvas del workflow](/img/workflow_canvas_toggle.webp) + +#### Con el trigger del workflow {#via-the-workflow-trigger} + +Para habilitar o deshabilitar un workflow desde su trigger, selecciona el ícono +del trigger en el Canvas y usa el interruptor del panel de configuración para +cambiar el estado del workflow. + +![Workflow habilitado en el panel del trigger](/img/via-trigger-panel.webp) + +### Runs manuales {#manual-runs} + +Mira el video para ver un resumen rápido. + + + +Puedes ejecutar un workflow manualmente de tres formas: + +#### Con una entrada vacía {#with-an-empty-input} + +Es el comportamiento predeterminado, y el dataclip de entrada de tu run será +`{}`. + + + +#### Con una entrada personalizada {#with-a-custom-input} + +Puedes escribir, copiar y pegar, o importar (buscándolo en tu sistema de +archivos o arrastrándolo y soltándolo) cualquier archivo con JSON válido. + + + +#### Con una entrada existente {#with-an-existing-input} + +Puedes elegir de una lista de entradas anteriores que se usaron para ejecutar +este step. + + + +### Dataclips con nombre {#named-dataclips} + +Puedes ponerles nombre a los dataclips (entradas personalizadas, resultados de +steps, solicitudes de webhook) para encontrarlos y usarlos más fácilmente en tus +pruebas. + +:::info Los dataclips con nombre no se borran + +Los dataclips con nombre no se eliminan junto con el resto del historial del +proyecto cuando se cumple el periodo de retención. Se guardan indefinidamente. + +::: + +Para ponerle un nombre a tu dataclip, haz clic en el campo de la etiqueta. + + + +Después de ponerles nombre a tus entradas, puedes buscarlas por nombre en la +barra de búsqueda. + + + +Para ver solo las entradas con nombre, haz clic en el botón de etiqueta. + + + +## Limitar la concurrencia {#limit-concurrency} + +La **concurrencia** de un workflow es la cantidad de runs que se permiten para +ese workflow **_al mismo tiempo_**. En OpenFn, los propietarios y +administradores del proyecto pueden limitar la cantidad máxima de runs de un +workflow que se ejecutan al mismo tiempo. Puede servirte para asegurar un +procesamiento en serie, "de uno en uno", o para evitar que un workflow rápido de +OpenFn supere el límite de solicitudes de la API de otro sistema conectado. + +:::info + +Comprueba que la ejecución en paralelo no esté deshabilitada en tu proyecto, +porque eso tiene prioridad sobre el límite de concurrencia del workflow. + +::: + +### ¿Qué pasa cuando un workflow tiene un límite de concurrencia? {#what-happens-when-concurrency-limit-is-set-on-a-workflow} + +Cuando un workflow tiene configurado un límite de concurrencia, la cantidad +máxima de runs que se ejecutan a la vez no supera el número fijado para ese +workflow. Por ejemplo: + +- **Concurrencia sin configurar (o = 0)**: no se aplica ningún límite + artificial, y este workflow solo está limitado por la capacidad de cómputo + total disponible en tu instalación de OpenFn. +- **Concurrencia = 1**: los runs de este workflow se ejecutan de uno en uno. + Cada run tiene que _terminar_ antes de que empiece el siguiente. +- **Concurrencia = 2**: no se pueden ejecutar más de 2 runs de este workflow a + la vez, y los demás runs tienen que quedarse en `enqueued`. Si los runs "A", + "B" y "C" están todos en cola, empiezan a ejecutarse "A" y "B". Cuando termina + "A", empieza "C". (Nunca más de 2 a la vez). + +### Configurar la concurrencia de un workflow {#setting-concurrency-for-a-workflow} + +Los límites de concurrencia se configuran en la ventana modal de configuración +del workflow, desde el Canvas del workflow. + +1. Haz clic en el ícono de configuración, junto al botón de guardar de tu + workflow, para abrir la configuración del workflow +2. En la ventana modal, escribe el límite máximo de concurrencia +3. Haz clic en guardar. + +![Configuración de la concurrencia](/img/configuring-concurrency.webp) + +### Salida de los logs {#log-outputs} + +Por motivos de seguridad de los datos y de cumplimiento normativo, puedes +configurar la salida de los logs de un run de workflow para que no registre las +sentencias `console.log()`. Un propietario o administrador del proyecto puede +hacerlo desde la ventana modal de configuración del workflow. + +1. Haz clic en el ícono de configuración. +2. En la ventana modal, desactiva el interruptor **Allow `console.log()` usage** + para dejar de registrar las sentencias `console.log()`. Está activado de + forma predeterminada. + +![Configuración de la salida de los logs](/img/configuring-log-outputs.webp) + +## Atajos de teclado {#keyboard-shortcuts} + +Desde el Canvas puedes hacer algunas acciones comunes (por ejemplo, guardar) con +el teclado. Consulta la lista completa de atajos de teclado +[aquí](/keyboard-shortcuts.md). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/build/working-with-branches.md b/i18n/es/docusaurus-plugin-content-docs/current/build/working-with-branches.md new file mode 100644 index 000000000000..67b8e41913aa --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/build/working-with-branches.md @@ -0,0 +1,63 @@ +--- +title: Gestionar cambios con ramas de GitHub +sidebar_label: Gestionar cambios +translation_source_hash: 550f3cd679d88a41a173cd21cd874d768048f718 +translation_review_status: machine +--- + +En la sección [Editar steps localmente](/build/editing-locally.md), vimos cómo +crear tus cambios y agregarlos a la rama `main` de un proyecto. + +Sin embargo, la mayoría de los cambios de código en los workflows implican +compartir y revisar los cambios antes de desplegarlos. Para hacerlo, puedes +crear, probar y compartir tus cambios en una rama nueva de GitHub y, cuando +estén listos, hacer merge en `main` para desplegarlos. + +:::tip + +Hay MUCHAS estrategias distintas para crear ramas y revisar código en Git. (¡Por +ejemplo, [GitHub Flow](https://guides.github.com/introduction/flow/) o +["ese famoso post de @nvie"](https://nvie.com/posts/a-successful-git-branching-model/)!) +Esta guía busca darte una introducción muy breve a las ramas en Git, pero no +pretende dictar la "forma correcta". + +::: + +Retomemos el proceso desde el momento en que hiciste `git pull` para traer los +últimos cambios del repositorio a tu carpeta local. + +1. Al ejecutar `git checkout -b {branch_name}`, se crea una rama nueva y te + cambias a ella. Cuando empieces a editar tus steps, los cambios se guardarán + en esta rama, separados de `main`. + +2. Para probar los cambios localmente, consulta la documentación de la + [CLI](/build-for-developers/cli-intro.md). + +3. Igual que cuando trabajas en `main`, cuando termines, revisa qué archivos + cambiaste con `git status`. + +4. Luego, usa `git add {filepath}` y después `git commit -m {change notes}` para + preparar los cambios para hacer merge en el repositorio. + +5. El siguiente comando envía tus cambios al repositorio remoto como una rama + nueva y separada: `git push --set-upstream origin {branch_name}`. + +6. En GitHub, puedes crear una pull request para que revisen y aprueben tus + cambios. + + ![PR-1](/img/pull-request.webp) + + ![PR-2](/img/pull-request-2.webp) + +7. A medida que trabajes con ramas, revisa en qué rama estás con `git status`. + +![git-status](/img/git-status.webp) + +8. Para mantener tu copia local al día con el repositorio remoto, cámbiate a + `main` con `git checkout main` y ejecuta `git pull` para traer los cambios. + +9. Si sigues trabajando en tu rama aparte mientras `main` se actualizó en el + remoto y quieres integrar esos cambios remotos, usa `git checkout main`, + luego `git pull`, luego `git checkout {working_branch_name}` y, por último, + `git merge main` para hacer merge de los cambios de `main` en tu rama de + trabajo. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/contribute/impact-tracker.md b/i18n/es/docusaurus-plugin-content-docs/current/contribute/impact-tracker.md new file mode 100644 index 000000000000..fcdf3699eecc --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/contribute/impact-tracker.md @@ -0,0 +1,77 @@ +--- +title: Rastreador de impacto +id: impact +translation_source_hash: 9cd3fac6b83ea2809ad16a7266363b379d84822d +translation_review_status: machine +--- + +## Introducción {#introduction} + +OpenFn es un bien público digital gratuito y de código abierto. Muchos usuarios +no pueden contribuir económicamente ni participar en nuestra comunidad de +desarrollo de producto, pero al enviar cada noche estos informes anónimos de uso +agregado aseguran la sostenibilidad del proyecto a largo plazo, porque: + +1. nos permiten entender las necesidades de nuestros usuarios, +2. demuestran mejor nuestro impacto, +3. y nos ayudan a mantener el apoyo de los donantes. + +:::success Rastreador de impacto anónimo + +Visita [openfn.org/impact](https://www.openfn.org/impact) para verlo en acción. + +::: + +¿Cómo funciona? Estas métricas ([ver más abajo](#the-data-yes-all-of-it)) son +anónimas y las envían operadores de instancias de todo el mundo. Cuando alguien +inicia OpenFn, lo primero que ve es un mensaje como el de abajo, que explica +exactamente qué datos anónimos agregados envía y adónde los envía. + +Según las instrucciones de instalación, los administradores de la instancia +pueden dejar de enviar métricas en cualquier momento con la variable de entorno +`USAGE_TRACKING_ENABLED`, ¡pero la mayoría prefiere contribuir! + +## Los datos. (Sí, todos.) {#the-data-yes-all-of-it} + +Si el administrador de un cliente de métricas envía datos de uso anónimos a +cualquier instancia de un servidor de métricas, esto es lo que se envía: + +```json +{ + "version": "2", + "instance": { + "version": "v2.4.2:match:f1bd9ae", + "hashed_uuid": "4CE189B993247E94FD2A9EDD28CEC9C9D5A7125AB85F4586A6C994D89DCC0979", + "no_of_users": 137, + "operating_system": "linux", + "no_of_active_users": 70 + }, + "projects": [ + { + "workflows": [ + { + "no_of_jobs": 1, + "no_of_runs": 6, + "hashed_uuid": "C08DD42A9DF75A017001429240D0E6C425BA89AF03C134A05E631CDB0A53FA87", + "no_of_steps": 6, + "no_of_active_jobs": 1 + }, + { + "no_of_jobs": 4, + "no_of_runs": 6, + "hashed_uuid": "BD70CCCA4D953D1B59F7803CC24A4EE84CFD21DE4F46CBA85FB3FA41AACA4EAD", + "no_of_steps": 24, + "no_of_active_jobs": 4 + }, + ... more workflows + ], + "hashed_uuid": "71A5B39B570E1E9156B73997C327E5A2FABD06507CE3FEBF85128016446FCD49", + "no_of_users": 6, + "no_of_active_users": 6 + }, + ... more projects + ], + "report_date": "2024-04-25", + "generated_at": "2024-04-26T01:30:00.876776Z" +} +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md b/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md new file mode 100644 index 000000000000..2dac73e664a8 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md @@ -0,0 +1,194 @@ +--- +title: Hoja de ruta y filosofía de producto +sidebar_label: Hoja de ruta +translation_source_hash: c9a9fc5e683db89572d83478f3c0327da01df9d7 +translation_review_status: machine +--- + +## Introducción {#introduction} + +Esta página detalla las hojas de ruta previstas para los productos clave de +OpenFn, incluidos Lightning, los adaptors y este sitio de documentación. Aunque +esta página se actualiza periódicamente, puedes seguir nuestra hoja de ruta y +nuestro progreso en tiempo real [aquí](#what-are-we-currently-working-on). + +## Nuestro enfoque del desarrollo de producto {#our-approach-to-product-development} + +En OpenFn seguimos el [enfoque Shape Up](https://basecamp.com/shapeup) para +ayudar a nuestro pequeño equipo de ingeniería a crear productos y funciones +_significativos_ más rápido, sin sacrificar la calidad. Con Shape Up, +normalmente nos comprometemos con _proyectos_ que se pueden entregar en un +período de 4 a 6 semanas, con varias versiones según la aprobación de QA dentro +del ciclo de desarrollo. También damos prioridad a los comentarios y las +solicitudes de funciones de nuestros usuarios por encima de las funciones del +backlog. + +:::success Transparencia y construir lo que importa + +Ten en cuenta que es bastante raro que nos comprometamos a entregar una función +concreta con más de 2 o 3 meses de antelación. Nos comprometemos a evaluar +constantemente lo que más necesitan nuestros usuarios y a dedicar los pocos +recursos que tenemos a aportarles valor; sencillamente no podemos garantizar que +lo que hoy parece la "séptima función más importante" siga en nuestra lista +dentro de 6 meses. + +Al mismo tiempo, nos esforzamos por ser lo más transparentes e inclusivos +posible en nuestros procesos de planificación. Tenemos un gran backlog de +solicitudes de funciones e issues de GitHub (reportes de bugs, borradores e +incluso épicas o proyectos parcialmente definidos) que reciben votos y +comentarios, y que nos sirven de inspiración al decidir qué priorizar a +continuación. + +¡Sigue leyendo para saber más sobre cómo trabajamos, cómo puedes ver lo que +viene y cómo puedes participar! + +::: + +## ¿En qué estamos trabajando ahora? {#what-are-we-currently-working-on} + +Todo el trabajo de nuestro equipo se sigue públicamente en un GitHub Project. +Tres vistas clave te muestran al minuto qué estamos haciendo y qué hay en +nuestra hoja de ruta inmediata. + +### Consulta [**_Now_**](https://github.com/orgs/OpenFn/projects/3/views/24?layout=table&sortedBy%5Bdirection%5D=desc&sortedBy%5BcolumnId%5D=Status) 🚧 para ver lo que se está construyendo ahora {#see-_now_httpsgithubcomorgsopenfnprojects3views24layouttablesortedby5bdirection5ddescsortedby5bcolumnid5dstatus--for-whats-currently-being-built} + +### Consulta [**_Next_**](https://github.com/orgs/OpenFn/projects/3/views/2?layout=table&sortedBy%5Bdirection%5D=desc&sortedBy%5BcolumnId%5D=Status) ⏭️ para ver lo que se está considerando para el próximo sprint {#see-_next_httpsgithubcomorgsopenfnprojects3views2layouttablesortedby5bdirection5ddescsortedby5bcolumnid5dstatus-️-for-whats-being-considered-for-the-next-sprint} + +### Consulta [**_Epics_**](https://github.com/orgs/OpenFn/projects/3/views/7) 🤔 para ver una lista de proyectos que estamos considerando, con una prioridad aproximada {#see-_epics_httpsgithubcomorgsopenfnprojects3views7--for-a-list-of-projects-that-were-considering-roughly-prioritized} + +### Consulta [**_Bugs_**](https://github.com/orgs/OpenFn/projects/3/views/22) 🐞 para ver los bugs conocidos que estamos siguiendo {#see-_bugs_httpsgithubcomorgsopenfnprojects3views22--for-known-bugs-were-tracking} + +Actualizaremos este sitio cada mes para reflejar nuestro progreso en los temas +principales. También puedes seguir en tiempo real todas las funciones nuevas, +cambios y correcciones de bugs en nuestro +[registro de cambios](https://openfn.github.io/lightning/changelog.html). + +## Cómo **participar** {#how-to-get-involved} + +Recopilamos comentarios y solicitudes de funciones nuevas en nuestro sitio de la +[comunidad](https://community.openfn.org/c/feature-requests/). Así, el equipo +principal de OpenFn y los usuarios pueden seguir y votar sus solicitudes +favoritas y las más críticas para su misión. + +:::info Únete a nuestra actualización semanal de producto + +Te animamos a unirte a nuestras actualizaciones semanales de producto, donde +presentamos las novedades y lo que viene. También es una buena oportunidad para +hacernos preguntas sobre OpenFn. La llamada es todos los viernes a las 11:00 GMT +(Londres) [aquí](https://meet.google.com/vaw-qvfq-mru) - +(https://meet.google.com/vaw-qvfq-mru). También puedes +[agregar nuestros eventos a tu calendario](https://calendar.google.com/calendar/u/0?cid=Y182Y2Y4NWY0NjlhNWVlMzA4NzEwMWE5MWNhYmRjZTRkMDZlZDU1OGY1OTM3ZGUzNTQ0NWNkYmQ2NDFhMDY3MGFjQGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20) +para no perderte nuestras demos, seminarios web y actualizaciones de producto. + +::: + +### Vota por funciones 👍 {#upvote-features-} + +1. Ve al + [tablero de solicitudes de funciones](https://community.openfn.org/c/feature-requests/) + de la comunidad +2. Desplázate hacia abajo o usa los filtros y la búsqueda para ver las + solicitudes existentes +3. Haz clic en el botón (Vote) junto al título de la solicitud para votarla +4. Si quieres más votos para esta solicitud, comparte un enlace a ella con tu + red + +### Solicita una función nueva 💡 {#request-a-new-feature-} + +1. Ve al + [tablero de solicitudes de funciones](https://community.openfn.org/c/feature-requests/) + de la comunidad +2. Haz clic en `+ New Topic` para crear una solicitud nueva. +3. Escribe un título muy claro, conciso y descriptivo para la función (por + ejemplo, "Hacer verde el botón de nuevo workflow") +4. Describe la solicitud en detalle y por qué es importante para ti. Ayuda si + puedes agregar imágenes y enlaces de referencia. +5. Comparte la solicitud en tu red profesional para conseguir votos + +:::info Consejo + +Al describir la función, nos ayuda mucho que expliques el problema, la solución +propuesta (si la hay) y soluciones similares de las que podamos sacar ideas, _si +existen_. + +::: + +### Abre un issue o reporta un bug directamente {#open-an-issue-or-bug-directly} + +Si prefieres ir directo, puedes buscar entre todos los issues que seguimos en la +organización de GitHub de OpenFn [aquí](https://github.com/OpenFn), comentarlos +o incluso encargarte de ellos. Si no encuentras lo que buscas, crea un issue en +el repositorio correspondiente. ¡Haremos lo posible por responder pronto! + + + +## ¿Tienes preguntas o comentarios, o encontraste un bug? {#have-questions-feedback-or-found-a-bug} + +Te animamos a publicar tus preguntas en la comunidad de OpenFn en +[community.openfn.org](https://community.openfn.org), o a crear issues para los +bugs en el repositorio del producto. También puedes empezar a contribuir por tu +cuenta al software, los adaptors o la documentación de OpenFn +[aquí](/contribute/writing-code.md). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/contribute/style-guide.md b/i18n/es/docusaurus-plugin-content-docs/current/contribute/style-guide.md new file mode 100644 index 000000000000..9558ef6ed3da --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/contribute/style-guide.md @@ -0,0 +1,262 @@ +--- +id: style-guide +title: Guía de estilo +sidebar_label: Guía de estilo +slug: /style-guide +translation_source_hash: 18fab29195b8958e596ca045c0f84dab79b66de4 +translation_review_status: machine +--- + +Puedes escribir contenido con la +[sintaxis de Markdown de GitHub](https://github.github.com/gfm/). + +:::tip + +Usamos un archivo `.prettierrc` para aplicar estilos estándar con el formateador +de código "Prettier". Si usas VS Code, puedes instalar Prettier desde +https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode + +Asegúrate de formatear tu trabajo antes de abrir una PR. + +::: + +## Sintaxis de Markdown {#markdown-syntax} + +Sirve como página de ejemplo para dar estilo a sitios de Docusaurus basados en +Markdown. + +## Encabezados {#headers} + +# H1 - Crea la mejor documentación {#h1---create-the-best-documentation} + +## H2 - Crea la mejor documentación {#h2---create-the-best-documentation} + +### H3 - Crea la mejor documentación {#h3---create-the-best-documentation} + +#### H4 - Crea la mejor documentación {#h4---create-the-best-documentation} + +##### H5 - Crea la mejor documentación {#h5---create-the-best-documentation} + +###### H6 - Crea la mejor documentación {#h6---create-the-best-documentation} + +--- + +## Énfasis {#emphasis} + +El énfasis, también llamado cursiva, con _asteriscos_ o _guiones bajos_. + +El énfasis fuerte, también llamado negrita, con **asteriscos** o **guiones +bajos**. + +Énfasis combinado con **asteriscos y _guiones bajos_**. + +El tachado usa dos virgulillas. ~~Tacha esto.~~ + +--- + +## Listas {#lists} + +1. Primer elemento de la lista ordenada +1. Otro elemento + - Sublista sin orden. +1. Los números reales no importan, solo que sea un número + 1. Sublista ordenada +1. Y otro elemento. + +- La lista sin orden puede usar asteriscos + +* O guiones + +- O signos más + +--- + +## Enlaces {#links} + +[Soy un enlace en línea](https://www.google.com/) + +[Soy un enlace en línea con título](https://www.google.com/ 'Página de inicio de Google') + +[Soy un enlace de +referencia][texto de referencia arbitrario sin distinción de mayúsculas] + +[Puedes usar números para definir enlaces de referencia][1] + +O déjalo vacío y usa el [propio texto del enlace]. + +Las URL, con o sin corchetes angulares, se convierten automáticamente en +enlaces. http://www.example.com/ o <http://www.example.com/> y a veces +example.com (pero no en GitHub, por ejemplo). + +Algo de texto para mostrar que los enlaces de referencia pueden ir más adelante. + +[texto de referencia arbitrario sin distinción de mayúsculas]: + https://www.mozilla.org/ +[1]: http://slashdot.org/ +[propio texto del enlace]: http://www.reddit.com/ + +--- + +## Imágenes {#images} + +Este es nuestro logo (pasa el cursor por encima para ver el texto del título): + +En línea: +![texto alternativo](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Texto del título del logo 1') + +De referencia: ![texto alternativo][logo] + + +[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png + 'Texto del título del logo 2' + + +Puedes usar imágenes de cualquier carpeta indicando la ruta al archivo. La ruta +debe ser relativa al archivo Markdown. + +![img](/img/undraw_Portfolio_update_re_jqnp.svg) + +### Tamaño y estilo de las imágenes {#image-sizingstyling} + +Puedes cambiar el tamaño de las imágenes con HTML en línea. + + + +--- + +## GIF {#gifs} + +Los GIF son útiles para mostrar secuencias cortas de acciones del usuario. + +![img](/img/how-to-gif.gif) + +Hay muchas herramientas que te ayudan a crear GIF: + +- [Peek](https://github.com/phw/peek) +- [Capture to a Gif](https://chrome.google.com/webstore/detail/capture-to-a-gif/eapecadlmfblmnfnojebefkbginhggeh) +- [Chrome Capture](https://chrome.google.com/webstore/detail/chrome-capture-screenshot/ggaabchcecdbomdcnbahdfddfikjmphe) + +:::note + +Si usas un "punto de cursor" animado y una "animación al mostrar o hacer clic", +el código hexadecimal que usamos es **#B53F48**. + +::: + +--- + +## Código {#code} + +```javascript +var s = 'JavaScript syntax highlighting'; +alert(s); +``` + +```python +s = "Python syntax highlighting" +print(s) +``` + +``` +No language indicated, so no syntax highlighting. +But let's throw in a tag. +``` + +```js {2} +function highlightMe() { + console.log('This line can be highlighted!'); +} +``` + +--- + +## Tablas {#tables} + +Puedes usar dos puntos para alinear las columnas. + +| Las tablas | son | geniales | +| ------------------ | :-------------------: | -------: | +| la col 3 está | alineada a la derecha | \$1600 | +| la col 2 está | centrada | \$12 | +| las rayas de cebra | quedan bien | \$1 | + +Debe haber al menos 3 guiones separando cada celda del encabezado. Las barras +verticales exteriores (|) son opcionales, y no hace falta que el Markdown sin +procesar quede bien alineado. También puedes usar Markdown en línea. + +| Markdown | menos | bonito | +| -------- | --------- | -------- | +| _Aún_ | `renders` | **bien** | +| 1 | 2 | 3 | + +--- + +## Bloques de cita {#blockquotes} + +> Los bloques de cita son muy útiles en el correo electrónico para imitar el +> texto de respuesta. Esta línea forma parte de la misma cita. + +Corte de cita. + +> Esta es una línea muy larga que se seguirá citando correctamente cuando se +> ajuste. Vaya, sigamos escribiendo para asegurarnos de que sea lo bastante +> larga como para que se ajuste en todas las pantallas. Ah, y puedes _poner_ +> **Markdown** en un bloque de cita. + +--- + +## HTML en línea {#inline-html} + +
+
Lista de definiciones
+
Es algo que la gente usa a veces.
+ +
Markdown en HTML
+
*No* funciona **muy** bien. Usa etiquetas HTML.
+
+ +--- + +## Saltos de línea {#line-breaks} + +Esta es una línea para empezar. + +Esta línea está separada de la anterior por dos saltos de línea, así que será un +_párrafo aparte_. + +Esta línea también es un párrafo aparte, pero... Esta línea está separada solo +por un salto de línea, así que es una línea aparte en el _mismo párrafo_. + +--- + +## Avisos {#admonitions} + +:::note + +Esto es una nota + +::: + +:::tip + +Esto es un consejo + +::: + +:::important + +Esto es importante + +::: + +:::caution + +Esto es una precaución + +::: + +:::warning + +Esto es una advertencia + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-code.md b/i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-code.md new file mode 100644 index 000000000000..ae291972649a --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-code.md @@ -0,0 +1,34 @@ +--- +title: Escribir código +translation_source_hash: 71b48081ff9089f5e2dd4f5125dc7ebc37e6fbd0 +translation_review_status: machine +--- + +## Introducción {#introduction} + +Esta sección ofrece a los desarrolladores una introducción básica para +contribuir a las aplicaciones de código abierto de OpenFn. + +Hay tres formas de contribuir a OpenFn, el bien público digital (DPG): + +### 1. Crea o amplía adaptors de OpenFn {#1-build-or-extend-openfn-adaptors} + +- Requiere conocimientos de JavaScript y TypeScript +- Consulta el [README.md](https://github.com/OpenFn/adaptors#contributing) para + saber cómo contribuir + +### 2. Agrega o mejora una funcionalidad de la plataforma OpenFn Lightning {#2-add-or-improve-a-feature-on-the-openfn-lightning-platform} + +- Requiere conocimientos de Elixir y Phoenix LiveView +- Consulta el + [README.md](https://github.com/OpenFn/lightning#contribute-to-this-project) + para saber cómo contribuir + +### 3. Amplía o mejora nuestra documentación {#3-add-to-or-improve-our-documentation} + +No dudes en señalar [issues](https://github.com/openfn/docs/issues) en la +documentación de OpenFn o, si no encuentras el repositorio adecuado, problemas +con las propias herramientas. (¡Cuantos más comentarios, mejor!) Si quieres +proponer texto nuevo para la documentación, ¡puedes hacer esos cambios con el +enlace **"Editar esta página"** al final de cualquier página y enviando una pull +request! diff --git a/i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-docs.md b/i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-docs.md new file mode 100644 index 000000000000..3ca3793ba3ea --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/contribute/writing-docs.md @@ -0,0 +1,67 @@ +--- +title: Escribir documentación +translation_source_hash: 9c0b7e14cd0181c531460200f8f126ea0a273c80 +translation_review_status: machine +--- + +No dudes en señalar [issues](https://github.com/openfn/docs/issues) en esta +documentación o, si no encuentras el repositorio adecuado, problemas con las +propias herramientas. (¡Cuantos más comentarios, mejor!) Si quieres proponer +texto nuevo para la documentación, ¡puedes hacer esos cambios con el enlace +**"Editar esta página"** al final de cualquier página y enviando una pull +request! + +## Introducción {#intro} + +Este documento es una guía para la documentación de OpenFn. Recuerda que el +objetivo es tratar "la documentación como código" y crear un portal de +documentación que permita usar las herramientas de OpenFn de forma bastante +autónoma. Puedes contribuir a este documento. + +## Qué es la documentación {#what-are-docs} + +Cuando hablamos de documentación, nos referimos a información concisa, precisa y +que evoluciona rápido, que ayuda a los integradores ciudadanos que usan OpenFn a +entender las interfaces complejas de la plataforma. ¿Qué significa tratar la +documentación como código? Guardar los archivos fuente en un sistema de control +de versiones. Generar los artefactos de la documentación automáticamente. +Asegurarse de que un grupo de revisores de confianza revise la documentación +minuciosamente. Publicar los artefactos con poca intervención humana. + +(Fuente: el libro de Anne Gentle +_[Docs Like Code](https://www.docslikecode.com/about/)_.) + +## Objetivos de esta documentación {#goals-for-these-docs} + +### Promover la colaboración {#promote-collaboration} + +Colabora con los contribuidores de forma eficiente: mantén la documentación en +el mismo sistema que el código y genera los entregables a partir de archivos +fuente. + +### Conseguir contribuciones de cola larga {#get-long-tail-contributions} + +Divide los entregables en partes que animen a hacer contribuciones pequeñas pero +valiosas. Ya no hace falta que una sola persona se encargue de todo un +entregable de documentación. + +### Seguir los bugs de la documentación como los bugs del código {#track-doc-bugs-like-code-bugs} + +Cuando corrijas un bug de la documentación, menciona ese bug en el mensaje del +commit para que los revisores puedan juzgar si la corrección resuelve el +problema planteado. + +### Conseguir revisiones rápidas y de calidad de los miembros del equipo {#get-prompt-and-good-quality-reviews-from-team-members} + +Confía en que los miembros del equipo valoren la documentación, garanticen la +precisión técnica y la coherencia, respeten las necesidades de los usuarios +finales y defiendan los mejores entregables de documentación para sus lectores. + +### Hacer documentación atractiva {#make-beautiful-docs} + +El diseño es importante. Crea documentación atractiva y moderna. + +### Usar herramientas y flujos de trabajo de desarrollo {#use-developer-tools-and-workflows} + +Automatiza el proceso lo más posible para que podamos centrarnos en crear +contenido. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/deploy/options.md b/i18n/es/docusaurus-plugin-content-docs/current/deploy/options.md new file mode 100644 index 000000000000..066dd2587e92 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/deploy/options.md @@ -0,0 +1,119 @@ +--- +title: Planificación +translation_source_hash: 0bf6b304d3c23534195b5a7bdc18065b53aec01c +translation_review_status: machine +--- + +## Introducción {#introduction} + +Puedes usar OpenFn como un servicio en la nube seguro, estable y escalable, o +desplegarlo localmente, con opciones administradas y no administradas. Sea cual +sea el camino que elijas, puedes configurar OpenFn para que ningún dato sensible +se guarde fuera de las fronteras de tu país. + +:::success Portabilidad + +Gracias a la [especificación de portabilidad](/deploy/portability.md) de OpenFn +y a sus herramientas de despliegue de código abierto, puedes pasar de una de +estas opciones a otra en cualquier momento. Nos comprometemos a que no dependas +de ningún proveedor (**no vendor lock-in**). + +::: + +| Opción | Nube gratuita | OpenFn Cloud | Dedicada | Hazlo tú mismo (DIY) | +| :-------------------------: | :-------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Descripción | Pasa a producción hoy mismo en OpenFn.org con proyectos de pequeña escala | Aumenta o reduce la escala y paga solo lo que necesitas | Una instalación de OpenFn dedicada y sin restricciones en cualquier parte del mundo, en nuestros servidores o en los tuyos | Despliega y administra tus propias soluciones con OpenFn | +| Licencia | Gratis para siempre, con límites de uso | **SaaS** ([planes](https://www.openfn.org/pricing)); contacta a enterprise@openfn.org para acuerdos personalizados o con factura | **SDaaS** incluye como servicio el despliegue, el mantenimiento, los parches de seguridad, las actualizaciones y la resolución de problemas; contacta a enterprise@openfn.org | La licencia LGPLv3 te permite usarlo libremente en cualquier solución cerrada o de código abierto, pero todas las obras _derivadas_ tienen que ser de código abierto | +| Ubicación | Infraestructura en la nube **global** y segura | Infraestructura en la nube **global** y segura | Infraestructura **local (en el país)** o **global** | Donde quieras | +| Despliegue | **Haz clic para empezar** en [OpenFn.org](https://www.openfn.org/signup) | **Haz clic para empezar** en [OpenFn.org](https://www.openfn.org/signup) | **Contacta a** enterprise@openfn.org | Lee esta página de la documentación y visita nuestro [GitHub](https://www.github.com/OpenFn) | +| Instalación y configuración | **Tú eliges**: configurarlo por tu cuenta, con un implementador certificado o con el equipo de OpenFn.org | **Tú eliges**: configurarlo por tu cuenta, con un implementador certificado o con el equipo de OpenFn.org | **Tú eliges**: configurarlo por tu cuenta, con un implementador certificado o con el equipo de OpenFn.org | **Tú eliges**: configurarlo por tu cuenta, con un implementador certificado o con el equipo de OpenFn.org | +| Soporte | Da y recibe soporte a través de la [comunidad](https://community.openfn.org) | Varios niveles a través de support@openfn.org | Varios niveles a través de support@openfn.org | Da y recibe soporte a través de la [comunidad](https://community.openfn.org) | + +## Ejemplo de plan de despliegue local {#sample-local-deployment-plan} + +:::info Esto es solo un ejemplo + +Tus requisitos serán distintos, pero este es un ejemplo de plan para lograr un +despliegue local a gran escala y con datos muy sensibles. + +::: + +Si estás considerando una implementación de OpenFn a gran escala o con datos muy +sensibles en servidores locales o administrados por el gobierno, podrías: + +1. **Ejecutar una prueba de concepto, un prototipo o una solución en producción + por tiempo limitado** con el servicio en la nube mientras determinas si se + ajusta a tus necesidades y qué valor aporta. (Es una forma más segura, más + barata y más rápida de demostrar el valor y la viabilidad de la solución en + sí). +2. Mientras se ejecuta la primera fase, **evaluar el valor y empezar los + preparativos**: + 1. Evalúa el **valor de la solución** en sí: ¿resuelve los problemas que + esperabas? + 2. Evalúa tus **requisitos de residencia de datos**: ¿necesitas ejecutar esta + solución en el país? + 3. Evalúa la **capacidad de DevOps** de tu equipo: ¿cómo van otros + despliegues locales de bienes públicos digitales (DPG)? + 4. Evalúa la infraestructura de cómputo, almacenamiento y redes de tu país: + ¿qué opciones\* hay disponibles para servidores y conectividad de red? + 5. Determina si lo mejor para tu ministerio es una solución en la nube de + **"persistencia cero"** o una solución **desplegada localmente**: con los + datos anteriores, haz un análisis de costo-beneficio de ambas opciones. +3. Trabajar con OpenFn.org o con un socio certificado para **practicar el + despliegue**, la migración, la reversión, el reinicio, las copias de + seguridad, etc. +4. Con las herramientas de portabilidad de OpenFn, **ejecutar una copia local** + de tu solución alojada en la nube para evaluar si tu despliegue local está + listo. +5. Establecer con OpenFn un **protocolo de conmutación por error** para "pasar a + la nube" en el caso de los sistemas críticos. + 1. ¿Con qué frecuencia se debería respaldar la configuración de la + implementación (no los datos sensibles) en la nube alojada por OpenFn.org? + 2. ¿A qué credenciales o entornos de prueba debería tener acceso la copia de + seguridad en la nube? + 3. Establece un plan para cambiar entre la nube y el despliegue local. +6. Establecer un **contrato de soporte** con proveedores locales certificados + por OpenFn o con el equipo principal de OpenFn para que te ayuden a mantener + el despliegue local si surgen problemas. +7. **Pasar por completo a tu despliegue local** y mantener la capacidad de dar + soporte a tu solución o de volver a desplegarla en otros servidores en la + nube o locales. +8. **Supervisar y ajustar tu estrategia** cuando haga falta, a medida que + evolucionan los requisitos de uso y de soberanía de datos de tu país. + +\*Visita la página [Requisitos](/deploy/requirements.md) para obtener más +información sobre las especificaciones de servidor recomendadas. + +## Pasar de la nube a un despliegue local (v1 o v2) {#moving-from-cloud-to-local-v1-or-v2} + +Si planeas una implementación autoalojada, te recomendamos desarrollar y probar +la solución inicial en el SaaS de OpenFn (v1 o v2, quizás en un plan gratuito) y +luego exportarla para usarla en Lightning (v2). + +Así, el implementador puede concentrarse en resolver los requisitos de negocio y +técnicos de la automatización antes de asumir los costos del despliegue. +Concéntrate en la solución, no en el despliegue. Después, cuando hayas hecho el +piloto, hayas demostrado su valor y estés listo para escalarla, puedes migrar tu +solución de OpenFn a un despliegue local de Lightning. + +### Recorrido de un usuario de OpenFn desplegado localmente {#a-user-journey-for-locally-deployed-openfn} + +1. Crea y prueba tus workflows en [OpenFn.org](https://www.openfn.org). +2. Exporta tu proyecto de OpenFn _como código_ con el botón "export" o con la + CLI de despliegue. +3. Despliega tu instancia local de OpenFn/Lightning. +4. Importa tu proyecto (del paso 2) a tu instancia local de OpenFn/Lightning con + la CLI de despliegue. +5. Vuelve a configurar tus credenciales (los secretos de las credenciales _no_ + se incluyen en la exportación). +6. Prueba tu proyecto desplegado localmente. + +## Guías técnicas {#technical-guidelines} + +Para ver la documentación detallada sobre el despliegue, visita la +[página de documentación para desarrolladores](https://openfn.github.io/lightning/readme.html) +de Lightning y presta especial atención a estas secciones: + +1. [Getting Started](https://openfn.github.io/lightning/readme.html#getting-started) +2. [Deployment Considerations](https://openfn.github.io/lightning/deployment.html) +3. [Benchmarking](https://openfn.github.io/lightning/benchmarking.md.html#run-benchmarking-tests-against-the-demo-webhook) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-v3.md b/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-v3.md new file mode 100644 index 000000000000..0b7e481bcec5 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-v3.md @@ -0,0 +1,447 @@ +--- +title: Portabilidad v3 (versión anterior) +translation_source_hash: 8c147d873969c77e43aa0cfd51b29ee7ae97a0ec +translation_review_status: machine +--- + +La especificación de portabilidad permite representar proyectos de workflows +completos "como código", permite a los usuarios moverse entre distintas formas +de despliegue (como en la nube, local o alojado) y propone una forma de definir +reglas de automatización de workflows y de integración de sistemas aplicable a +nivel global, que se podría usar en todos los motores de workflows y plataformas +de integración del sector. + +Nada en la especificación _tiene_ que ser exclusivo de OpenFn ni de ninguno de +nuestros productos. Imaginamos un futuro en el que el software creado con +Lightning, el OpenFn Integration Toolkit y herramientas de integración o de +workflows completamente nuevas y distintas puedan adoptar esta especificación. + +Si te interesa contribuir a la especificación, contacta a OpenFn a través del +[foro de la comunidad](https://community.openfn.org), escríbenos o sugiere +cambios enviando una pull request aquí. + +:::warning + +Esta es la versión anterior de la especificación de portabilidad de OpenFn. + +Para ver la versión más reciente, consulta [Portabilidad](portability) + +::: + +## Proyectos "como código" {#projects-as-code} + +Los proyectos completos (grupos de workflows con sus triggers, edges, +credenciales y jobs) se pueden representar como código. + +Esto mejora la experiencia de desarrollo en OpenFn porque (a) permite crear y +probar workflows localmente; (b) permite el control de versiones de los +proyectos y un registro de auditoría de sus cambios; y (c) permite a los +usuarios trasladar proyectos existentes entre distintas instancias (es decir, +despliegues) de Lightning. + +### Estructura de directorios {#directory-structure} + +Muchos usuarios guardan sus proyectos de OpenFn en repositorios de git, y esta +es una estructura habitual: + +``` +myProject/ +├── workflow-a/ +│ ├── job-1.js +│ ├── job-2.js +│ └── job-3.js +├── workflow-b/ +│ └── job-4.js +├── project.yaml +├── projectState.json +└── config.json +``` + +:::info Estructura de directorios + +Hay 3 estructuras de directorios que se suelen usar en los proyectos de OpenFn: +estándar, producción y prueba, y monorepo. + +::: + +### La **_especificación_** del proyecto {#the-project-spec} + +La especificación del proyecto (o "spec") se suele guardar en un archivo +`project.yaml`. Aunque la mayor parte de la especificación se escribe +directamente en el archivo, muchos desarrolladores prefieren guardar el cuerpo +de sus jobs en archivos `.js` aparte y hacer referencia a ellos con una ruta +relativa. + +```yaml +name: openhie-project +description: Some sample +credentials: + jane-smith@test.com-HAPI-FHIR: + owner: jane-smith@test.com + name: HAPI FHIR +workflows: + OpenHIE-Workflow: + name: OpenHIE Workflow + jobs: + FHIR-standard-Data-with-change: + name: FHIR-standard-Data-with-change + adaptor: '@openfn/language-http@latest' + enabled: true + credential: null + body: + path: ./jobs/my-fancy-script.js + + Send-to-OpenHIM-to-route-to-SHR: + name: Send-to-OpenHIM-to-route-to-SHR + adaptor: '@openfn/language-http@latest' + enabled: true + credential: jane-smith@test.com-HAPI-FHIR + body: | + fn(state => { + console.log("hello github integration") + return state + }); + + Notify-CHW-upload-successful: + name: Notify-CHW-upload-successful + adaptor: '@openfn/language-http@latest' + enabled: true + credential: null + body: fn(state => state); + + Notify-CHW-upload-failed: + name: Notify-CHW-upload-failed + adaptor: '@openfn/language-http@latest' + enabled: true + credential: null + body: + path: ./jobs/notify-failure.js + + triggers: + webhook: + type: webhook + edges: + webhook->FHIR-standard-Data-with-change: + source_trigger: webhook + target_job: FHIR-standard-Data-with-change + condition: always + FHIR-standard-Data-with-change->Send-to-OpenHIM-to-route-to-SHR: + source_job: FHIR-standard-Data-with-change + target_job: Send-to-OpenHIM-to-route-to-SHR + condition: on_job_success + Send-to-OpenHIM-to-route-to-SHR->Notify-CHW-upload-successful: + source_job: Send-to-OpenHIM-to-route-to-SHR + target_job: Notify-CHW-upload-successful + condition: on_job_success + Send-to-OpenHIM-to-route-to-SHR->Notify-CHW-upload-failed: + source_job: Send-to-OpenHIM-to-route-to-SHR + target_job: Notify-CHW-upload-failed + condition: on_job_failure +``` + +En esta especificación puedes ver las distintas formas de definir el cuerpo de +un job: + +1. Cuerpo en línea: se usa en los jobs `FHIR-standard-Data-with-change` y + `Send-to-OpenHIM-to-route-to-SHR`. El cuerpo se escribe directamente en el + archivo YAML. + +2. Referencia a un archivo externo: se usa en los jobs + `Notify-CHW-upload-successful` y `Notify-CHW-upload-failed`. El cuerpo se + guarda en archivos aparte, a los que se hace referencia con la clave path. + Así se puede organizar mejor la lógica de los jobs complejos. + +Al usar rutas de archivo: + +- Las rutas son relativas a la ubicación del archivo `project.yaml`. +- Asegúrate de que los archivos a los que haces referencia existen y contienen + código válido para el cuerpo de un job. +- Este método es especialmente útil para jobs complejos o cuando quieres + reutilizar el cuerpo de un job en distintos proyectos. + +### El **_estado_** del proyecto {#the-project-state} + +El estado del proyecto es una representación de un proyecto concreto tal como +está _en una instancia específica de Lightning_. Se suele guardar como +`projectState.json` y contiene los UUID de los recursos en un despliegue +concreto de Lightning. + +```json +{ + "id": "8deff39d-8189-4bd7-9dc7-f9f08e7f2c60", + "name": "openhie-project", + "description": null, + "inserted_at": "2023-08-25T08:57:31", + "updated_at": "2023-08-25T08:57:31", + "scheduled_deletion": null, + "requires_mfa": false, + "project_credentials": { + "jane-smith@test.com-HAPI-FHIR": { + "id": "25f48989-d349-4eb8-99c3-923ebba5b116", + "name": "HAPI FHIR", + "owner": "jane-smith@test.com" + } + }, + "workflows": { + "OpenHIE-Workflow": { + "id": "27ae2937-0959-48b8-a597-b1646aae8c14", + "name": "OpenHIE Workflow", + "jobs": { + "Transform-data-to-FHIR-standard": { + "id": "e44f65bb-5038-4e17-8d93-b63cbe95254a", + "delete": true + }, + "Send-to-OpenHIM-to-route-to-SHR": { + "id": "977b87ff-f347-42b5-832f-6ae2ca726f32", + "name": "Send-to-OpenHIM-to-route-to-SHR", + "adaptor": "@openfn/language-http@latest", + "body": "fn(state => state);\n", + "enabled": true + }, + "Notify-CHW-upload-successful": { + "id": "86b743a3-fd00-4629-b9fb-d5f38fb56d0b", + "name": "Notify-CHW-upload-successful", + "adaptor": "@openfn/language-http@latest", + "body": "fn(state => state);\n", + "enabled": true + }, + "Notify-CHW-upload-failed": { + "id": "be85df30-0abd-4f8e-be17-501f67e18b8d", + "name": "Notify-CHW-upload-failed", + "adaptor": "@openfn/language-http@latest", + "body": "fn(state => state);\n", + "enabled": true + }, + "FHIR-standard-Data": { + "id": "55016dda-42e3-4ee1-8a9c-24e3f23d42f1", + "delete": true + }, + "FHIR-standard-Data-with-change": { + "id": "28dd0846-a6ae-40c0-8ab4-3e0a6b487afe", + "name": "FHIR-standard-Data-with-change", + "adaptor": "@openfn/language-http@latest", + "body": "fn(state => state);\n", + "enabled": true + } + }, + "triggers": { + "webhook": { + "id": "530cde0b-0de4-4f68-8834-0a4356a2fe53", + "type": "webhook" + } + }, + "edges": { + "webhook->Transform-data-to-FHIR-standard": { + "id": "b2c7407b-0ae9-4ca5-9d6b-ee624976fa54", + "delete": true + }, + "Transform-data-to-FHIR-standard->Send-to-OpenHIM-to-route-to-SHR": { + "id": "d22ed6f4-26a2-4c85-b261-cc110a6851e6", + "delete": true + }, + "Send-to-OpenHIM-to-route-to-SHR->Notify-CHW-upload-successful": { + "id": "26c12f7f-7806-4008-87cd-6747998f95f4", + "condition": "on_job_success", + "source_job_id": "977b87ff-f347-42b5-832f-6ae2ca726f32", + "source_trigger_id": null, + "target_job_id": "86b743a3-fd00-4629-b9fb-d5f38fb56d0b" + }, + "Send-to-OpenHIM-to-route-to-SHR->Notify-CHW-upload-failed": { + "id": "0630ac96-4f67-4de7-8c3d-0bf3f89f80d9", + "condition": "on_job_failure", + "source_job_id": "977b87ff-f347-42b5-832f-6ae2ca726f32", + "source_trigger_id": null, + "target_job_id": "be85df30-0abd-4f8e-be17-501f67e18b8d" + }, + "webhook->FHIR-standard-Data": { + "id": "5ce3a8ed-b9eb-464a-a2cd-ba55adc393c2", + "delete": true + }, + "FHIR-standard-Data->Send-to-OpenHIM-to-route-to-SHR": { + "id": "5f459cd9-2882-4a61-a2cc-ec45e58d4837", + "delete": true + }, + "webhook->FHIR-standard-Data-with-change": { + "id": "75e7f7d8-274b-410d-9600-730bbd535229", + "condition": "always", + "source_job_id": null, + "source_trigger_id": "530cde0b-0de4-4f68-8834-0a4356a2fe53", + "target_job_id": "28dd0846-a6ae-40c0-8ab4-3e0a6b487afe" + }, + "FHIR-standard-Data-with-change->Send-to-OpenHIM-to-route-to-SHR": { + "id": "1e5ba385-2c49-4241-8cd2-042c99a810ec", + "condition": "on_job_success", + "source_job_id": "28dd0846-a6ae-40c0-8ab4-3e0a6b487afe", + "source_trigger_id": null, + "target_job_id": "977b87ff-f347-42b5-832f-6ae2ca726f32" + } + } + } + } +} +``` + +## Usar la CLI para interactuar con proyectos {#using-the-cli-interact-with-projects} + +La especificación y el estado de un proyecto se pueden usar con distintos fines. +Por ejemplo, puedes generar el estado y la especificación como copias de +seguridad del proyecto, o generar estos archivos y usarlos para auditorías y +registros. La [CLI](https://github.com/OpenFn/kit/tree/main/packages/cli) de +OpenFn incluye comandos para descargar la configuración de un proyecto desde un +servidor de Lightning en ejecución, y para desplegar o enviar cambios a +proyectos existentes en un servidor de Lightning. Para saber más sobre el +control de versiones automatizado con pull y deploy, consulta nuestra +documentación sobre [control de versiones](/manage-projects/link-to-gh.md). + +:::info ¿Todavía no tienes la CLI? + +Instálala ejecutando `npm install -g @openfn/cli` + +::: + +Antes de usar la CLI, configúrala con variables de entorno: + +``` +OPENFN_ENDPOINT=https://app.openfn.org +OPENFN_API_KEY=yourSecretApiToken +``` + +O con un archivo `config.json`: + +```json +{ + // Required, can be overridden or set with `OPENFN_API_KEY` env var + "apiKey": "***", + + // Optional: can be set using the -p, defaults to project.yaml + "specPath": "project.yaml", + + // Optional: can be set using -s, defaults to .state.json + "statePath": ".state.json", + + // Optional: defaults to OpenFn.org's API, can be overridden or set with + // `OPENFN_ENDPOINT` env var + "endpoint": "https://app.openfn.org" +} +``` + +Puedes encontrar más detalles sobre la CLI +[aquí](https://github.com/OpenFn/kit/tree/main/packages/cli#basic-usage). + +### `openfn pull` para generar la especificación y el estado {#openfn-pull-to-generate-spec--state} + +Para generar los archivos de especificación y de estado de un proyecto +existente, usa: + +```sh +openfn pull {YOUR-PROJECT-UUID} -c ./config.json +``` + +Este comando guarda (o sobrescribe) un archivo de especificación y uno de estado +del proyecto según la ruta que hayas definido en tu configuración. + +### `openfn deploy` para crear proyectos nuevos {#openfn-deploy-to-create-new-projects} + +Para desplegar un proyecto nuevo en una instancia de Lightning a partir de un +archivo de especificación (sin un archivo de estado), usa: + +```sh +openfn deploy -c config.json +``` + +### `openfn deploy` para actualizar proyectos existentes {#openfn-deploy-to-update-existing-projects} + +Con un estado de proyecto válido definido en tu `config.json`, el mismo comando +`openfn deploy` envía tus cambios según la diferencia entre la especificación de +tu proyecto y lo que hay en el servidor. + +```sh +openfn deploy -c config.json +Checking https://demo.openfn.org/api/provision/4adf2644-ed4e-4f97-a24c-ab35b3cb1efa for existing project. +Project found. +[CLI] ♦ Changes: + { + workflows: [ + { + jobs: [ + { +- body: "fn(state => {\n console.log(\"ok\")\n return state\n});" ++ body: "fn(state => {\n console.log(\"some changes here!\")\n return state\n});\n" + } + ... + ... + ... + ] + } + ] + } + +? Deploy? yes +[CLI] ♦ Deployed. +``` + +## Obtener ayuda con la CLI {#getting-help-with-the-cli} + +El paquete de la CLI incluye una ayuda integrada (`help`). Si agregas `--help` a +un comando, como `openfn deploy --help`, verás un mensaje de ayuda que describe +el comando y las opciones disponibles al usarlo. Mira este ejemplo: + +```sh +openfn deploy --help +openfn deploy + +Deploy a project's config to a remote Lightning instance + +Options: + --version Show version number [boolean] + --help Show help [boolean] + -c, --config, --config-path The location of your config file [default: "./.config.json"] + --no-confirm Skip confirmation prompts (e.g. 'Are you sure?') [boolean] + --describe Downloads the project yaml from the specified instance [boolean] + -l, --log Set the log level [string] + --log-json Output all logs as JSON objects [boolean] + -p, --project-path The location of your project.yaml file [string] + -s, --state-path Path to the state file +``` + +## Resolución de problemas {#troubleshooting} + +Esta sección explica cómo resolver algunos errores que podrías encontrar al usar +pull o deploy de OpenFn en tus proyectos. + +### Extraneous Workflow ID {#extraneous-workflow-id} + +#### Descripción {#description} + +Este error ocurre cuando ejecutas `openfn deploy` y los ID de los workflows de +tu projectSpec no coinciden con los de tu instancia de OpenFn. Cuando esto pasa, +el error se muestra en un objeto de error como este: + +``` +[CLI] ✘ Failed to deploy project openfn-data-buffers-prototype: +{ + "errors": { + "workflows": { + "1-ingest-messages": { + "base": [ + "extraneous parameters: workflow_id" + ] + }, + "2-calculate-indicators": { + "base": [ + "extraneous parameters: workflow_id" + ] + } + } + } +``` + +#### Solución {#solution} + +Ejecuta `openfn pull` para actualizar tu instancia local y mantener los ID +sincronizados, incorpora tus cambios y vuelve a ejecutar `openfn deploy`. + +## Otras versiones {#other-versions} + +- [Especificación de portabilidad v2](portability-versions#v2) +- [Especificación de portabilidad v1](portability-versions#v1) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-versions.md b/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-versions.md new file mode 100644 index 000000000000..4be40ab1c7b0 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability-versions.md @@ -0,0 +1,116 @@ +--- +title: Versiones de la propuesta de portabilidad +translation_source_hash: 6f60d19ea47e10d8a98feb1db3e323c0fe2297d1 +translation_review_status: machine +--- + +Nuestro compromiso con la portabilidad no ha cambiado a lo largo de la historia +de OpenFn, pero la forma de abordarlo y de ponerlo en práctica ha tomado muchas +formas. + +Este documento sirve de referencia para las versiones anteriores de la +especificación. + +## v3 {#v3} + +El estándar v3 se creó para la plataforma v2 y está vinculado al proyecto +Lightning. + +La v3 usa los comandos y protocolos de despliegue antiguos de la CLI. La app y +la CLI todavía la admiten por completo, pero se está retirando desde mayo +de 2026. + +[Consulta la especificación v3 aquí](/documentation/deploy/portability-v3) + +## v2 {#v2} + +Se usa para exportar desde la plataforma antigua. + +```yaml +jobs: + job-1: + expression: > + registerPatient({ + patient-id: state.data.id, + dob: state.data.birth + }) + adaptor: '@openfn/language-openmrs' + trigger: trigger-1 + credential: my-secret-credential + recurring-job: + expression: > + fn(state => { + console.log("Hi there!") + return state; + }) + adaptor: '@openfn/language-common' + trigger: every-minute + flow-job: + expression: > + fn(state => { + state.data.number = state.data.number * 3 + return state; + }) + adaptor: '@openfn/language-common' + trigger: after-j1 + catch-job: + expression: > + fn(state => { + state.message = "handled it." + return state; + }) + adaptor: '@openfn/language-common' + trigger: j1-fails + +triggers: + trigger-1: + criteria: '{"number":2}' + every-minute: + cron: '* * * * *' + after-j1: + success: job-1 + j1-fails: + failure: job-1 + +# Note that credential keys get copied, but values must be manually entered +# after the export is completed. +credentials: + my-secret-credential: + username: '******' + password: '******' +``` + +## v1 {#v1} + +Propuesta inicial de portabilidad + +```js +const project = { + async: true, + triggers: { + uniqueTriggerId: { + // trigger properties + }, + otherTrigger: { + // other trigger properties + }, + }, + credentials: { + // for now, credentials will not be synced // + // secret1: { + // username: 'mamadou', + // pass: 'shhh', + }, + staticData: { + // static objects that can be accessed from any job + }, + jobs: { + payHealthWorker: { trigger: 'otherTrigger' }, + syncToSalesforce: { + expression: 'uri://github.com/jobs/expresion.js', + trigger: 'uniqueTriggerId', + credential: 'secret1', + }, + }, +}; +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability.md b/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability.md new file mode 100644 index 000000000000..7c1224180afb --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/deploy/portability.md @@ -0,0 +1,197 @@ +--- +title: Portabilidad +translation_source_hash: 9f8f8f055402d673cb33c47b2f12fe9087308afb +translation_review_status: machine +--- + +La especificación de portabilidad es una idea central de los proyectos de +OpenFn. Es a la vez un estándar técnico y un compromiso continuo. Garantiza que +el código escrito en una aplicación de OpenFn se pueda: + +- Desplegar en otra instancia de OpenFn (algo clave para los servicios en + producción que se ejecutan en el país) +- Ejecutar en una máquina local (una gran noticia para quienes desarrollan + workflows o adaptors) +- Sacar por completo de OpenFn y ejecutar en un entorno de ejecución de + JavaScript genérico + +Este manifiesto es la base de las funcionalidades principales de OpenFn Sync, +CLI Deploy, la fusión de sandboxes y la exportación e importación de proyectos +desde la app. + +:::info Especificaciones de portabilidad anteriores + +Nuestro compromiso con la portabilidad no ha cambiado a lo largo de la historia +de OpenFn, pero la forma de abordarlo y de ponerlo en práctica ha tomado muchas +formas. + +Este documento describe la especificación de portabilidad más reciente, +publicada en mayo de 2026. Para ver las especificaciones anteriores, consulta +[Versiones de portabilidad](/deploy/portability-versions.md) + +::: + +Nada en la especificación _tiene_ que ser exclusivo de OpenFn ni de ninguno de +nuestros productos. Imaginamos un futuro en el que el software creado con +Lightning, el OpenFn Integration Toolkit y herramientas de integración o de +workflows completamente nuevas y distintas puedan adoptar esta especificación. + +Si te interesa contribuir a la especificación, contacta a OpenFn a través del +[foro de la comunidad](https://community.openfn.org), escríbenos o sugiere +cambios enviando una pull request aquí. + +## Proyectos como código {#projects-as-code} + +Un principio fundamental de los proyectos de OpenFn es que se pueden representar +como código, en un sistema de archivos o en una rama de git. + +Esto mejora la experiencia de desarrollo en OpenFn porque: + +1. Permite crear y probar workflows localmente +2. Permite el control de versiones de los proyectos y un registro de auditoría + de sus cambios +3. Permite a los usuarios trasladar proyectos existentes entre distintas + instancias (es decir, despliegues) de Lightning. + +## Especificación de proyecto {#project-spec} + +La unidad de portabilidad, lo que codifica un proyecto y permite compartirlo, +sincronizarlo, desplegarlo y editarlo, se llama especificación de proyecto +(project spec). Es una definición abstracta de un proyecto, un plano que se +puede desplegar en muchos lugares. + +Esta estructura define un conjunto de workflows y, para cada workflow, su +configuración principal y la secuencia de steps que ejecuta. Normalmente la +representamos en YAML, porque es cómodo tanto para las personas como para las +máquinas, pero se puede representar en cualquier formato de texto. + +Con una copia de la especificación de un proyecto, puedes: + +- Importar un proyecto a una instancia de la app de OpenFn +- Ejecutar workflows localmente con la CLI +- Desplegar un proyecto en una instancia de la app de OpenFn +- Fusionar proyectos sandbox localmente + +A medida que se introducen nuevas funcionalidades, se agregan claves a esta +estructura con regularidad. Esperamos y nos aseguramos de que todas las +aplicaciones de la especificación admitan estas claves. + +Puedes exportar la especificación de un proyecto desde la app, en la página +Settings. + +Los workflows también se pueden intercambiar por separado con la misma +especificación. Así, puedes importar un workflow a un proyecto existente, o +ejecutarlo localmente sin clonar todo el proyecto. + +## Ejemplo de especificación {#spec-example} + +Este es un ejemplo de especificación de proyecto en formato YAML: + +```yaml +id: portability-example +name: Portability Example +schema_version: '4.0' +collections: + - my-data-cache +credentials: + - name: my-login + owner: some-user@openfn.org +workflows: + - name: Event-based workflow + steps: + - id: transform-data + name: Transform data + expression: fn(s => s) + adaptor: '@openfn/language-common@latest' + - id: webhook + type: webhook + webhook_reply: before_start + enabled: true + next: + transform-data: + disabled: false + condition: always + id: event-based-workflow + start: webhook + - name: Scheduled workflow + steps: + - id: common + name: Common + expression: fn(s => s) + adaptor: '@openfn/language-common@3.3.1' + - id: cron + type: cron + enabled: true + cron_expression: 00 00 * * 1-5 + cron_cursor_job_id: get-data + next: + get-data: + disabled: false + condition: always + - id: get-data + name: Get data + expression: fn(s => s) + adaptor: '@openfn/language-http@latest' + configuration: editor@openfn.org|local login + next: + throw-error: + disabled: false + condition: on_job_failure + common: + disabled: false + condition: '!state.error' + label: sometimes + never: + disabled: true + condition: on_job_success + - id: never + name: never + expression: fn(s => s) + adaptor: '@openfn/language-http@7.2.10' + - id: throw-error + name: throw error + expression: fn(s => s) + adaptor: '@openfn/language-common@3.3.1' + id: scheduled-workflow + start: cron +``` + +El esquema más reciente de un archivo de especificación de proyecto está +definido en TypeScript en +[portability.d.ts](https://github.com/OpenFn/kit/blob/main/packages/lexicon/portability.d.ts). + +## Sincronizar proyectos {#syncing-projects} + +Para saber más sobre cómo desplegar, ejecutar, descargar y editar un proyecto, +consulta nuestra documentación detallada sobre +[CLI Sync](/build-for-developers/cli-sync.md). + +## Recursos vinculados {#linked-resources} + +Aunque diseñamos los proyectos pensando en la portabilidad, algunas +funcionalidades NO son portables por naturaleza. + +Por ejemplo, las credenciales contienen tokens muy sensibles que, por diseño y +por naturaleza, deberían ser muy difíciles de extraer de la plataforma de +OpenFn. Por eso, las credenciales no son realmente portables. Al exportar un +proyecto, las credenciales sensibles no se deberían incluir en ese documento +exportado en texto plano. + +Del mismo modo, las colecciones son una funcionalidad muy ligada a un despliegue +concreto de una plataforma de OpenFn. La especificación de portabilidad no cubre +los datos de las colecciones (aunque, con los permisos adecuados, se pueden +sincronizar datos entre colecciones). + +Este tipo de recursos no portables no forman parte de un proyecto, pero están +VINCULADOS a un proyecto. + +Normalmente, los recursos se vinculan por nombre. Las credenciales y las +colecciones solo declaran que dependen de algo con un nombre determinado, que se +tiene que resolver en tiempo de ejecución. La CLI tiene herramientas para eso, y +al desplegar en una instancia de destino, puede que haya que configurar antes la +instancia para que tenga recursos que coincidan. + +## Especificaciones de portabilidad anteriores {#legacy-portability-specifications} + +Para ver versiones anteriores de nuestro enfoque, consulta +[Versiones de portabilidad](/deploy/portability-versions.md) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/deploy/requirements.md b/i18n/es/docusaurus-plugin-content-docs/current/deploy/requirements.md new file mode 100644 index 000000000000..58cf30039ef1 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/deploy/requirements.md @@ -0,0 +1,170 @@ +--- +title: Requisitos +translation_source_hash: a9076ecb0a1f8904b390ca7474d5443302a760f0 +translation_review_status: machine +--- + +## Planifica primero {#plan-first} + +¿No sabes por dónde empezar? Vuelve a la página +[Planificación](/deploy/options.md) para pensar cómo quieres escalar tus +proyectos de automatización con OpenFn. + +## Evalúa tu capacidad {#assess-your-capacity} + +:::info Ayuda a tu socio a estimar los costos iniciales y continuos + +Usa estas preguntas para empezar a evaluar tu capacidad y tus recursos técnicos, +para que tu socio de despliegue pueda estimar mejor el costo total de propiedad. + +::: + +1. ¿Cómo se despliegan, supervisan y mantienen actualmente las aplicaciones en + la nube en tu organización o gobierno? Cada entorno de despliegue y cada + institución es única, y OpenFn es flexible: según tus procesos actuales de + DevOps, te recomendaremos distintos mecanismos de despliegue. +2. ¿Qué personal de TI y de DevOps hay disponible para apoyar el despliegue y el + mantenimiento de OpenFn? ¿Ese personal tiene experiencia con Docker y + Kubernetes? ¿Y con bases de datos Postgres? +3. ¿El despliegue requerirá alta disponibilidad? (Es decir, si OpenFn va a + recibir solicitudes en tiempo real desde otras aplicaciones en lugar de + ejecutar jobs basados en cron, se deberían ejecutar al menos dos instancias + de OpenFn a la vez detrás de un balanceador de carga, usando "Erlang + distribuido" para lograr una redundancia de la aplicación sin interrupciones. + Si OpenFn no va a recibir solicitudes y solo va a hacer solicitudes salientes + con un horario cron, donde el momento exacto importa poco, mantener un + sistema sin tiempo de inactividad es algo menos importante). + +## Conocimientos necesarios {#knowledge-requirements} + +| Habilidad | Importancia y motivo | +| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Erlang | La **capa de aplicación web y orquestación** de OpenFn es una aplicación Erlang OTP. | +| JavaScript | Los **workers que procesan los jobs** de OpenFn y los propios workflows de OpenFn se basan en JavaScript. Si sabes cómo funciona Node.js, puedes crear workflows que hagan _cualquier cosa_. | +| Postgres | La **base de datos** predeterminada de OpenFn es PostgreSQL | +| Docker | Publicamos todas las **[imágenes](https://hub.docker.com/repository/docker/openfn/lightning/general) de OpenFn** en Docker Hub. Tanto si quieres simplificar la configuración para desarrolladores como si usas tecnologías de orquestación de contenedores, te será útil entender Docker y la computación en contenedores. | +| Kubernetes | En los despliegues de alta disponibilidad, los servicios de Kubernetes ofrecen **balanceo de carga** y simplifican la **administración de contenedores** en varios hosts. Facilitan que las aplicaciones de una empresa sean más escalables, flexibles, portables y productivas. | + +## Requisitos de las máquinas {#machine-requirements} + +:::tip Si eliges "DIY", empieza por lo simple + +Kubernetes _NO_ es obligatorio, pero se recomienda para los despliegues de alta +disponibilidad. Para una configuración más simple, considera un despliegue con +Docker o en servidores físicos (las aplicaciones Erlang OTP funcionan muy bien +en Linux). + +::: + +El SaaS oficial de OpenFn usa [Kubernetes](https://kubernetes.io/) para los +despliegues administrados en Google Cloud, y lo recomendamos para despliegues +escalables y de alta disponibilidad. Con cargas de trabajo variables, es +importante (por estabilidad y por costos) poder escalar el grupo de nodos y los +pods de la aplicación Erlang OTP por separado del grupo de nodos y los pods de +los workers de JavaScript. + +1. Usar un servicio SQL escalable y mantener _al menos_ dos nodos de la + aplicación en ejecución con las siguientes especificaciones ayudará a evitar + tiempos de inactividad no deseados. + 1. **Solicitudes de GKE (requests):** cpu@ "500m", memory@ "1024Mi" + 2. **Límites de GKE (limits):** memory@ "2560Mi" +2. Para despliegues simples sin Kubernetes ni alta disponibilidad, las máquinas + mínimas recomendadas son: + - **Máquina de la aplicación:** 2 vCPU (más o menos un solo núcleo de un + Intel Xeon E5 de 2.6 GHz) con 3.75 GB de memoria y 15 GB de almacenamiento + para la aplicación + 1. Cualquier sistema operativo basado en Linux que pueda ejecutar Docker + (Ubuntu 20.04+ o Debian 9+). + 2. Docker (18 o superior). + - **Máquina de la base de datos:** 2 vCPU (más o menos un solo núcleo de un + Intel Xeon E5 de 2.6 GHz) con 3.75 GB de memoria. El almacenamiento que + necesita la base de datos depende de cuántos días de datos de mensajes + quieras guardar en la propia aplicación (si quieres guardar alguno), y no + se puede determinar sin estimar el volumen de mensajes y runs. Si ampliar + el almacenamiento físico no es difícil en tu despliegue, empieza con 40 GB. + 1. Una instancia de Postgres (como mínimo v14.2), ejecutada en un _servidor + distinto_ del de la aplicación para lograr más estabilidad. +3. Si la aplicación y la base de datos están alojadas en la misma máquina (lo + que no se recomienda), esa máquina debería tener aproximadamente la suma de + los requisitos anteriores. +4. **Ten en cuenta** que, de forma predeterminada, la aplicación ofrece un + endpoint HTTP (sin TLS/SSL). Se espera que un proxy inverso o balanceador de + carga proporcione HTTPS (compatible con HTTP2) y balanceo de carga entre las + instancias. + - _Es decir, el servidor de la aplicación no cifra el acceso web, así que + hace falta un servidor web delante de la aplicación; Nginx con certificados + TLS es un buen punto de partida._ +5. Aunque la arquitectura de red depende del cliente, **recomendamos firmemente + una subred privada** para los servidores de la aplicación. +6. No hace falta desplegar la aplicación de OpenFn en la misma máquina que otros + servicios. Sin embargo, si los sistemas de origen y destino están alojados en + otros servidores, habrá que configurar el enrutamiento de red y las reglas + del firewall para que la integración pueda acceder a ellos. +7. Para la **resolución de problemas y el soporte externo**, los administradores + necesitarán acceso SSH a una cuenta sin restricciones (`sudo` en Ubuntu) si + se requieren servicios de mantenimiento del despliegue. + +## Configuraciones posibles {#possible-configurations} + +Aunque deberías planificar con cuidado tu estrategia de despliegue junto con un +especialista en DevOps, estas configuraciones de ejemplo pueden servirte como +punto de partida. + +### (a) Simple {#a-simple} + +Despliega la aplicación y la base de datos en la misma máquina. + +```mermaid +flowchart TB + subgraph "Linux VM with Docker" + ex1-.-db1 + direction TB + ex1(Erlang OTP App with JS Worker) + db1[(PostgreSQL)] + end +``` + +### (b) Mínima recomendada {#b-recommended-minimum} + +Despliega la aplicación y la base de datos en máquinas distintas. + +```mermaid +flowchart TB + ex1-.-db1 + subgraph "Linux VM with Docker" + direction LR + ex1(Erlang OTP App)-.-js1(Node.js Worker App) + end + subgraph "Linux VM" + db1[(PostgreSQL)] + end +``` + +### (c) Ideal {#c-ideal} + +Escala automáticamente distintos grupos de nodos optimizados en un clúster de +Kubernetes para la aplicación de orquestación en Erlang y la aplicación de +workers de JavaScript. + +Considera usar Postgres como servicio con alta disponibilidad, o ejecutarlo +también en un clúster. + +```mermaid +flowchart TB + ex1-.-db1 + ex1-.-js1 + lb1-->ex1 + subgraph "Load Balancer" + lb1(Ingress) + end + subgraph "VMs/Node Pools for Erlang apps" + direction LR + ex1(Erlang OTP Apps) + end + subgraph "VMs/Node Pool for JS Worker Apps" + js1(Node.js Worker Apps) + end + subgraph "VMs/Node Pool" + db1[(PostgreSQL)] + end +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/design/api-discovery.md b/i18n/es/docusaurus-plugin-content-docs/current/design/api-discovery.md new file mode 100644 index 000000000000..c5a5ff4854e0 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/design/api-discovery.md @@ -0,0 +1,158 @@ +--- +sidebar_label: Descubrimiento de APIs +title: Descubrimiento de APIs para diseñar workflows +translation_source_hash: a378a6fb2934089eafffc5b52421baaedcd7dde2 +translation_review_status: machine +--- + +# Descubre APIs para orientar el diseño de tu automatización de workflows {#discovering-apis-to-inform-your-workflow-automation-design} + +Este artículo explica cómo analizar la documentación de una API y hacer un +borrador del diagrama técnico del workflow. + +## ¿Qué es una API? {#what-is-an-api} + +Las APIs les dicen a las aplicaciones cómo comunicarse. Una API es el +"mensajero" que: + +1. Te dice cómo armar una solicitud, +2. Entrega tu solicitud al proveedor al que se la haces y, después, +3. Te devuelve la respuesta + +| ![Workflow](/img/api_diagram.webp) | +| :-----------------------------------------------------------------: | +| _[Fuente](https://snipcart.com/blog/integrating-apis-introduction)_ | + +OpenFn se conecta con las APIs mediante solicitudes HTTP enviadas por la web. +OpenFn puede automatizar cualquier tarea que permitan las APIs de las +aplicaciones con las que se integra (por ejemplo, si la API de una aplicación +permite enviar pagos, OpenFn puede automatizar el envío de pagos). + +## Cómo analizar la documentación de una API {#how-to-analyze-api-documentation} + +Al principio del proceso de diseño, deberías explorar la documentación de la API +del sistema de destino para ver las opciones de integración. + +### Define las opciones de integración {#determine-integration-options} + +Considera estas preguntas para definir tus opciones de integración, aunque no +haya una API disponible: + +1. ¿Hay una API RESTful? + - Si la hay, ¡OpenFn puede conectarse sin configuración adicional! La API + REST es el estándar de referencia de la mayoría de las aplicaciones web + modernas y suele admitir el formato de datos JSON. +2. ¿Hay un webhook? + - La mayoría de las aplicaciones móviles de recolección de datos ofrecen esta + función. Algunas la llaman "data forwarding", "web callback" o "HTTP push + API". + - Los webhooks envían mensajes o notificaciones automáticamente cuando pasa + algo (por ejemplo, cuando se envía un formulario nuevo, avisan a servicios + externos como OpenFn). Estas notificaciones basadas en eventos permiten la + integración de datos en tiempo real o acciones automatizadas. +3. Si no, ¿qué otras opciones hay para importar y exportar datos de las + aplicaciones de destino? + - ¿Puedes conectarte directamente a una base de datos? + - ¿Hay una forma de importar y exportar archivos? (JSON, CSV, XLS o XML) + - ¿Hay una API heredada (por ejemplo, SOAP) con la que podamos comunicarnos + mediante solicitudes HTTP? + +:::tip + +OpenFn puede conectar cualquier aplicación, aunque no tenga API. Consulta la +sección ["Adaptors"](/adaptors) para saber más. + +::: + +### Autenticación {#authentication} + +La documentación de una API suele tener una sección dedicada a las opciones de +autenticación. Búscala para ver qué métodos de autenticación admite y si hará +falta configurar algo para crear un usuario o una credencial de API nueva. + +Ten en cuenta que los métodos de autenticación con claves de API u OAuth suelen +ser más seguros que la autenticación básica (usuario y contraseña). + +:::tip + +Pide cuanto antes una credencial de API al administrador del sistema de la +aplicación con la que quieres integrarte. Así podrás probar la autenticación en +un entorno de desarrollo o de pruebas y comprobar que puedes conectarte. + +::: + +### Endpoints de la API {#api-endpoints} + +Analiza la documentación para ver qué recursos o entidades y qué funciones +admite la API. Por ejemplo, si quieres registrar pacientes mediante la API, +busca referencias al endpoint "/patients" (o como se llame este recurso en tu +aplicación de destino). + +Esta sección de la documentación incluye un resumen de los métodos de solicitud +HTTP (es decir, POST, GET, etc.) y los parámetros de solicitud que se admiten, +además de ejemplos de solicitudes HTTP que puedes enviar a la API. + +**Los métodos de solicitud HTTP te indican qué operaciones admite la API.** + +1. **C**reate (crear) → POST +2. **R**ead (leer) → GET +3. **U**pdate (actualizar) → PUT o PATCH +4. **D**elete (eliminar) → DELETE + +Por ejemplo, si quieres consultar registros de pacientes de una aplicación, +fíjate si la documentación de la API incluye `GET /patients`. + +### Límites {#limits} + +Presta atención a los límites de la API. La documentación suele tener una +sección dedicada que describe si hay límites o consideraciones sobre las +solicitudes y su frecuencia, la concurrencia y la cantidad de registros. Conocer +estos límites desde el principio te ayuda a diseñar una integración que dé una +automatización escalable y de alto rendimiento. + +## Diagrama técnico del workflow {#technical-workflow-diagramming} + +El resultado del descubrimiento de APIs debería ser un diagrama "técnico" del +workflow. A diferencia del diagrama funcional que se hace durante el +["Descubrimiento"](/design/discovery.md), este refleja las especificaciones +técnicas para integrarse con las aplicaciones de destino. Esas especificaciones +incluyen los métodos u operaciones concretos (por ejemplo, GET o POST) y los +nombres de los recursos de destino en la base de datos o la API (es decir, los +endpoints concretos de la API o las tablas concretas de la base de datos). + +![Workflow](/img/api_example.webp) + +**Al hacer el borrador de tus especificaciones técnicas, ten en cuenta lo +siguiente:** + +1. **Prepárate para los fallos. Tus workflows van a fallar. Piensa qué pasa + cuando fallen…** + - ¿Hay que avisar a alguien? + - ¿Cómo se puede volver a procesar el workflow de forma segura? + - ¿Cómo te aseguras de que no se creen datos duplicados? +2. **Cuando sea posible, usa identificadores únicos para crear una + automatización idempotente. Busca registros existentes en el sistema de + destino con algún identificador único disponible:** + - UUID de registros del sistema (por ejemplo, record_id: asjd2910-b8zy1s0a), + - Códigos únicos (por ejemplo, HOUSEHOLD-10013) y + - Combinaciones únicas de atributos (por ejemplo, familyName + phoneNumber + + village + districtCode) +3. **Si el sistema de destino no tiene una operación "upsert" nativa ni + comprueba duplicados antes de insertar, implementa un patrón upsert ("update + or insert", actualizar o insertar) para…** + - Comprobar si un registro existe con un identificador único… + - Si existe, actualizar el registro. + - Si no, insertar un registro nuevo. +4. **No olvides tener en cuenta los volúmenes de datos. Según tengas que manejar + 1, 10 000 o más de un millón de registros, puede que tengas que cambiar el + enfoque del workflow.** + - Estima el tamaño de los datos que vas a extraer + - Ten en cuenta los límites de la API (registros por página, límites de + frecuencia de solicitudes) + - Considera las operaciones masivas y las solicitudes por lotes + +Mira abajo el diagrama técnico del workflow para sincronizar envíos de +formularios de KoboToolbox con DHIS2. El diagrama funcional original está +[aquí](/design/discovery.md#workflow-requirements-gathering). + +![Workflow](/img/technical_example.webp) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/design/design-overview.md b/i18n/es/docusaurus-plugin-content-docs/current/design/design-overview.md new file mode 100644 index 000000000000..dbc3a062c809 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/design/design-overview.md @@ -0,0 +1,66 @@ +--- +sidebar_label: Resumen del proceso de diseño +title: Resumen del proceso de diseño +translation_source_hash: 089b874cb2e0c86bf464a47396fff6ee07e7cb54 +translation_review_status: machine +--- + +Este artículo describe a grandes rasgos los pasos para diseñar workflows +automatizados, a partir del proceso de implementación estándar del equipo +principal de OpenFn. + +Por lo general, el diseño se hace fuera de OpenFn, en conversación y +colaboración con las personas involucradas del lado del negocio o del programa y +del lado técnico. Una vez cerrado el diseño, la configuración, las pruebas, el +monitoreo y la gestión del workflow se hacen en OpenFn. + +## Términos clave {#key-terms} + +Antes de empezar, asegúrate de entender bien estos términos clave, que usaremos +en toda esta documentación: + +### Workflow (flujo de trabajo) {#workflow} + +El conjunto de instrucciones que determinan cómo resolver un problema o realizar +una tarea. A menudo se divide en tareas más pequeñas e independientes. + +![Workflow](/img/workflow.webp) + +### Automatización de workflows {#workflow-automation} + +El uso de software para realizar estas tareas de forma autónoma, de acuerdo con +reglas de negocio predefinidas y sin necesidad de intervención humana. + +![Automatización de workflows](/img/workflow_automation.webp) + +### Integración de datos {#data-integration} + +El proceso de combinar datos de distintas fuentes en una vista centralizada. La +integración de datos es una forma de lograr la automatización de workflows. Sus +tareas pueden simplificarse, automatizarse y gestionarse con una herramienta de +automatización de workflows. + +![Integración de datos](/img/data_integration.webp) + +## Introducción {#introduction} + +El diseño de la automatización de workflows tiene 5 pasos principales, que se +explican en detalle en otros artículos: + +1. [Descubrimiento y alcance](/design/discovery.md) +2. [Diseño del workflow](/design/design-workflow.md) +3. [Descubrimiento de APIs y diseño técnico](/design/api-discovery.md) +4. [Especificaciones de mapeo de elementos de datos](/design/mapping-specs.md) +5. [Especificaciones del workflow](/design/workflow-specs.md) + +### Caso de uso de ejemplo {#example-use-case} + +En toda la documentación de diseño usaremos como referencia este escenario +ficticio de recolección de datos y automatización de workflows: + +_PatientCare es una ONG de salud con una red de trabajadores comunitarios de +salud que atienden a pacientes en zonas remotas de Guinea. Los trabajadores de +PatientCare recolectan datos de pacientes en +[KoboToolbox](https://www.kobotoolbox.org/). El gobierno de Guinea usa +[DHIS2](http://dhis2.org) como su sistema nacional de información de salud (HIS) +y exige que PatientCare registre todos los datos de pacientes en el HIS._ diff --git a/i18n/es/docusaurus-plugin-content-docs/current/design/design-workflow.md b/i18n/es/docusaurus-plugin-content-docs/current/design/design-workflow.md new file mode 100644 index 000000000000..eac6600fcc44 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/design/design-workflow.md @@ -0,0 +1,75 @@ +--- +sidebar_label: Diseño del workflow +title: Diseña tu primer workflow para automatizar +translation_source_hash: cac640729c28a69e35500d79306c90cf3bcec87a +translation_review_status: machine +--- + +# Diseña tu primer workflow de OpenFn {#designing-your-first-openfn-workflow} + +Este artículo explica cómo usar la información reunida durante el descubrimiento +para definir los pasos concretos del workflow, diseñarlo y hacer un borrador de +diagrama que documente los pasos del proceso que quieres automatizar. + +## ¿Por qué hacer un diagrama del workflow? {#why-diagram-your-workflow} + +Al recopilar los requisitos, puedes esbozar el nuevo workflow con una lista de +pasos o partir de la documentación de un proceso o protocolo de negocio que ya +exista. **Por ejemplo:** + +1. Un paciente nuevo visita la clínica +2. Un trabajador registra al paciente en la aplicación móvil (KoboToolbox) +3. Todos los días, se sincronizan los pacientes nuevos con el sistema nacional + de información de salud (DHIS2) + +Después, considera representar visualmente la estructura y el flujo del workflow +para que las distintas personas involucradas lo entiendan con más facilidad. Un +diagrama ayuda a reflejar: + +1. El flujo o la secuencia correcta de pasos, +2. Las dependencias, +3. Las redundancias y +4. Quién es responsable de cada paso + +## Pasos principales para hacer el diagrama de un workflow {#main-steps-to-workflow-diagramming} + +1. Haz el diagrama de los pasos humanos o manuales del proceso de este workflow, +2. Identifica oportunidades de automatización, +3. Detalla los pasos funcionales del proceso de automatización ideal, +4. Comparte el diagrama con todas las personas involucradas para la aprobación + final y actualízalo cuando haga falta + +El resultado de este ejercicio es una documentación clara de cómo se ejecutará +un proceso de negocio: con automatización, con personas o, a menudo, con una +combinación de ambas. + +## Usa estándares globales en tus diagramas {#diagram-using-global-standards} + +Al hacer diagramas, considera usar estándares globales como BPMN (modelo y +notación de procesos de negocio), para que sean coherentes y los entiendan +personas de fuera. BPMN (más información sobre el estándar +[BPMN 2.0](https://www.omg.org/spec/BPMN/2.0/)) tiene símbolos parecidos a los +de un diagrama de flujo y una notación precisa que se puede traducir a +componentes de procesos de software. + +Estos recursos te ayudarán a aprender y a crear tus propios diagramas BPMN: + +- `BPMN.io`, modelador de código abierto: https://bpmn.io/ +- `Camunda BPMN Tool` incluye una herramienta gratuita y un tutorial: + https://camunda.com/bpmn/ +- `LucidChart` ofrece una interfaz para hacer diagramas muy fácil de usar: + https://www.lucidchart.com/pages/bpmn + +¿Buscas un curso rápido? Este video da un resumen rápido de BPMN y de cómo +usarlo: https://www.youtube.com/watch?v=BwkNceoybvA + +### Ejemplos de diagramas BPMN de OpenFn {#openfn-examples-of-bpmn-diagrams} + +Mira el diagrama BPMN de ejemplo de abajo para esta historia de usuario: + +> Como gerente de programa, quiero extraer los datos de los beneficiarios +> ("tracked entity instances") del sistema DHIS2 de mi país para inscribirlos +> como contactos en mi campaña de SMS configurada en RapidPro y enviarles +> alertas automáticas y novedades del programa. + + diff --git a/i18n/es/docusaurus-plugin-content-docs/current/design/discovery.md b/i18n/es/docusaurus-plugin-content-docs/current/design/discovery.md new file mode 100644 index 000000000000..d250260c3cc9 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/design/discovery.md @@ -0,0 +1,173 @@ +--- +sidebar_label: Descubrimiento y alcance +title: Descubrimiento y alcance de proyectos de OpenFn +translation_source_hash: c971b06c2192e638cce500ea16236908d8832c45 +translation_review_status: machine +--- + +# Descubrimiento y alcance de proyectos de OpenFn {#discovery--scoping-for-openfn-projects} + +Este artículo reúne las preguntas clave de descubrimiento y alcance para +confirmar el valor de negocio, los requisitos centrales del workflow, la +viabilidad técnica y la capacidad del cliente al empezar una implementación +nueva. Se basa en el caso de uso de ejemplo presentado en la +[introducción de la serie](/design/design-overview.md#example-use-case). + +:::tip + +Para exportar o compartir rápidamente estas preguntas, consulta esta +[presentación](https://docs.google.com/presentation/d/1WIc_uNAqapILF7redhTnZXpPRo1jFPSmAjoAplGt42w/edit?usp=sharing). + +::: + +## Preguntas clave {#key-questions} + +### Evaluación del valor de negocio {#business-value-assessment} + +El primer paso del descubrimiento es evaluar el valor de negocio: el posible +retorno de la inversión, las mejoras de eficiencia y otros resultados valiosos +que servirán para medir el éxito. + +**Preguntas que hacer:** + +1. ¿Qué workflows quieres automatizar? +2. ¿Cómo se gestionan actualmente esos workflows? + - ¿Hay un proceso de negocio manual o semiautomático? + - Si lo hay, ¿cuánto tiempo del personal se dedica a gestionarlos? +3. ¿Qué problemas resolverá la automatización? ¿Qué eficiencias o beneficios se + obtendrán? ¿Cuál es el costo de no hacer nada? + - Si no automatizamos estos workflows, ¿cómo seguirán las cosas? + - ¿El workflow actual es lento o inseguro, o empeora la calidad de los datos + o la prestación de servicios? + +**Ejemplo:** + +1. Quiero automatizar la sincronización de datos de casos de KoboToolbox con + DHIS2 Tracker. +2. Actualmente nuestro equipo dedica 3 horas a la semana a exportar a mano los + datos de Kobo e ingresarlos en DHIS2. +3. Esta automatización eliminará el riesgo de error humano al ingresar los datos + a mano, nos ahorrará dinero y tiempo, y nos permitirá atender a más + pacientes. + +## Recopilación de requisitos del workflow {#workflow-requirements-gathering} + +Usa las preguntas de abajo para definir los pasos concretos del workflow. El +resultado debería ser un borrador del workflow o un diagrama del proceso de +negocio (considera hacerlo en [BPMN](https://www.bpmn.org/) para usar una +notación estándar). + +**Preguntas que hacer:** + +1. ¿Qué dispara el workflow y con qué frecuencia debería ejecutarse? (por + ejemplo, en tiempo real o programado) + - ¿Hay una acción de un usuario o un evento del sistema que debería disparar + el workflow? (por ejemplo, en tiempo real al enviar un formulario, o cuando + el estado de un registro cambia a "closed") + - ¿O debería programarse para un día y una hora concretos? (por ejemplo, + todos los días a las 12:00) +2. ¿El workflow necesita un flujo de datos en un solo sentido o en ambos? + - Por ejemplo, si el workflow envía un registro del sistema A al sistema B, + ¿los datos solo tienen que ir en un sentido? ¿O, una vez sincronizados en + el sistema B, hay que devolver algo al sistema A para tener un flujo de + datos bidireccional? +3. ¿Qué volúmenes de datos se esperan? (por ejemplo, 100 derivaciones al mes o + 12 000 formularios al año) + +**Ejemplo:** el workflow debería sincronizar los datos de pacientes de +KoboToolbox con DHIS2 cada vez que se envía un formulario (es decir, +sincronización en tiempo real). Se registran como máximo 5000 pacientes al mes +en Kobo. + +![Workflow](/img/functional_example.webp) + +### Evaluación de viabilidad técnica {#technical-feasibility-assessment} + +Las respuestas a las preguntas de abajo te ayudarán a hacer un borrador del +diagrama de la solución, que documenta exactamente qué instancias se conectarán +y qué interfaces de integración se usarán. + +1. ¿Cuántas instancias hay de los sistemas de destino? (es decir, ¿te conectas a + 1 o a 2 instancias de base de datos?) +2. ¿Los sistemas de destino ya están construidos? ¿Se espera que cambie alguna + configuración? + - Si la configuración todavía está en curso, considera retomar este proyecto + cuando los sistemas estén estables. +3. ¿Hay una API REST disponible? + - Si la hay, comparte la documentación. + - Si todavía no la hay, considera retomar este proyecto cuando la API esté + construida y probada. + - Si no la hay, ¿qué otros métodos hay para importar y exportar datos? + - ¿Es posible conseguir una conexión directa a la base de datos? + - ¿O hay un webhook u otro método para reenviar datos a un sistema externo? + - ¿O una forma de exportar e importar datos con archivos? ¿Qué formatos de + datos hay disponibles? +4. ¿Dónde están alojados los sistemas de destino? ¿Hay requisitos de seguridad o + consideraciones de autenticación conocidas? (por ejemplo, firewalls, + requisitos de VPN o listas blancas de IP) +5. ¿Hay un entorno de pruebas al que podamos acceder para probar la integración + con la aplicación? (Si no lo hay, ¿hay una demo pública de la aplicación que + corra en la misma versión que usas actualmente, para que podamos probar las + APIs?) + +**Ejemplo:** para esta integración solo hay una instancia de PatientCare y una +de DHIS2, y ya están construidas con APIs REST. Las dos están alojadas en +servidores gestionados por PatientCare que exigen una lista blanca de IP para +acceder. + +![Workflow](/img/technical_example.webp) + +### Evaluación de capacidades {#capacity-assessment} + +Las respuestas a las preguntas de abajo te ayudarán a definir los roles del +proyecto para diseñar y entregar la implementación, y a planificar la +capacitación, el despliegue, la administración continua y el soporte. + +1. ¿Cada sistema de destino tiene un administrador de sistemas a tiempo + completo? + - ¿Los administradores pueden apoyar la configuración y las pruebas de la + integración? + - ¿Los administradores podrán ofrecer un entorno de pruebas o de desarrollo? + - ¿Quién aprenderá a administrar OpenFn? +2. ¿Qué conocimientos técnicos tienen? + - ¿Qué otros recursos hay para dar soporte continuo? + - ¿Alguien en la organización tiene experiencia con JavaScript o JSON? +3. ¿Hay interés en aprender a gestionar la implementación de OpenFn de forma + independiente? +4. ¿Quién en la organización se encargará de la gobernanza continua de la + solución y de supervisar la gestión de cambios? ¿Hay recursos para reunirse + con regularidad y revisar las solicitudes de cambio? + +**Ejemplo:** + +| Nombre | Rol | +| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ian | Administrador del sistema OpenFn, que se encargará de la gestión y el monitoreo continuos | +| Melody | Administradora de PatientCare, que capacitará en el workflow a los usuarios de su sistema | +| Arnis | Administrador de DHIS2, que capacitará en el workflow a los usuarios de su sistema | +| Ramona | Punto focal de programas, que defenderá los intereses de los usuarios, aportará información para definir los requisitos del workflow y se reunirá con regularidad con los usuarios para recoger comentarios, proponer cambios y revisar las solicitudes de cambio con los administradores de sistemas | + +### Documentar la arquitectura de la solución {#documenting-the-solution-architecture} + +Una vez que hayas reunido los requisitos clave de la solución, considera crear +un diagrama de "arquitectura de la solución" que documente lo siguiente: + +1. Los distintos componentes de la solución +2. Los flujos de datos entre esos componentes (destacando el intercambio de + datos dentro de la organización y con servicios de terceros) +3. Los tipos de datos que se intercambian +4. Los puntos de autenticación y acceso + +Estos diagramas aportan transparencia, ayudan a detectar posibles riesgos de +exposición de datos y documentan el cumplimiento de los requisitos de protección +de datos. Mira los diagramas de arquitectura de ejemplo de abajo. + +**Ejemplo 1:** + +![Workflow](/img/solution_diagram1.webp) + +**Ejemplo 2:** + +| ![Workflow](/img/solution_diagram2.webp) | +| :------------------------------------------------------------------------------------------------------------------------------------------------: | +| _[Fuente](https://lucid.app/lucidchart/1e997197-2d67-4393-8394-a532d83561b2/edit?invitationId=inv_85b809a1-6fbd-4275-abdc-618fbd56e90d&page=0_0#)_ | diff --git a/i18n/es/docusaurus-plugin-content-docs/current/design/mapping-specs.md b/i18n/es/docusaurus-plugin-content-docs/current/design/mapping-specs.md new file mode 100644 index 000000000000..915755b1ef9c --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/design/mapping-specs.md @@ -0,0 +1,181 @@ +--- +sidebar_label: Especificaciones de mapeo +title: Escribir especificaciones de mapeo de elementos de datos +translation_source_hash: 12c62347cedde52a1f09d8219e7a03ed1a9792e2 +translation_review_status: machine +--- + +# Mapear elementos de datos para definir las reglas de integración y automatización de datos {#mapping-data-elements-to-define-data-integration--automation-rules} + +Este artículo recorre el proceso de mapeo de elementos de datos con el que se +definen especificaciones, a nivel de entidad y de campo, de cómo se deben +intercambiar, limpiar o transformar los datos en un workflow de integración de +datos. En pocas palabras, el mapeo de datos es el proceso de conectar un campo +de datos de una fuente con un campo de datos de otra (por ejemplo, "patient" en +el sistema A = "person" en el sistema B). + +Una especificación de mapeo de elementos de datos es un tipo especial de +diccionario de datos que sirve como (1) documentación de cómo traduces el +significado entre sistemas y (2) especificación para los desarrolladores que +construyen la solución de automatización del workflow. + +Para cada paso de automatización de tu workflow, documentarás qué elementos de +datos (o metadatos) se usan y las "reglas" para mapearlos, reasignarlos, +limpiarlos, transformarlos o calcularlos. + +![mapeo](/img/mapping_example.webp) + +**Para hacer un borrador de especificación de mapeo de elementos de datos, +tendrás que…** + +1. Exportar los metadatos o pedir una lista de elementos de datos de los + sistemas de destino, +2. Conseguir un registro de "entrada" de muestra del sistema de origen y un + registro de salida de muestra del sistema de destino. En el mejor de los + casos, es un payload JSON de ejemplo o un enlace a registros de ejemplo. En + el peor, es una captura de pantalla o un archivo CSV con datos "ficticios". +3. ¡Empezar a "mapear" los elementos de datos y a registrar las reglas de + transformación! + +| ![mapeo](/img/mapping_process.webp) | +| :---------------------------------------------------------------------: | +| _El proceso de mapeo de datos para soluciones de integración de datos._ | + +## Plantilla de especificación de mapeo de OpenFn {#openfn-mapping-specification-template} + +Puedes documentar elementos de datos, mapeos y reglas con la plantilla de +especificación de mapeo de OpenFn. El equipo de OpenFn creó esta +[plantilla](https://docs.google.com/spreadsheets/d/19sPRLP4zeFgFbtOL1wKh-rc7D0KPMu3etmOOG_x5t68/edit#gid=1275153608) +a partir de lo aprendido al implementar soluciones de integración de datos para +ONG y socios gubernamentales de todo el mundo. Se usa en todos los proyectos de +OpenFn y la mantiene el equipo de OpenFn. + +## Consideraciones sobre el mapeo {#mapping-considerations} + +### Mantener las especificaciones de mapeo {#maintaining-mapping-specifications} + +Cuando tu proyecto de OpenFn esté en producción, el documento de +especificaciones de mapeo puede ser la forma en que tus usuarios interactúan con +tu solución, en un lenguaje que entiende el negocio. Si haces cambios, asegúrate +de que la especificación de mapeo siempre coincida con el código de tus jobs. +Considera también versionar tus especificaciones de mapeo para que las personas +involucradas tengan acceso a las implementaciones anteriores de la solución. + +### Mapeo funcional y mapeo técnico {#functional-vs-technical-mapping} + +Una vez que tu organización (o "el negocio") defina las reglas funcionales de +mapeo de elementos de datos entre los sistemas de origen y de destino, tendrás +que ver qué otros elementos de datos técnicos hacen falta para que la +integración funcione. Pueden ser campos propios del sistema, IDs o parámetros de +la API que funcionan "por detrás" y que el usuario final quizá no vea, pero que +el sistema de destino necesita para compartir los datos. + +### Variables globales y reglas de mapeo {#global-variables-and-mapping-rules} + +A veces, al mapear listas de valores o conjuntos de opciones (por ejemplo, +listas de diagnósticos, jerarquías geográficas o la lista de servicios que +ofrece la organización en todas partes), esos valores son "globales" y hay que +usarlos una y otra vez en toda la implementación del workflow. + +Por ejemplo, imagina que tu aplicación de origen tiene una lista de IDs de +ubicación codificados (por ejemplo, `01, 02, 03`) que hay que mapear a una lista +global de valores de ubicación o unidades administrativas: + +```js +//source location IDs: destination location values +01: 'Western Cape', +02: 'Eastern Cape', +03: 'Gauteng' +``` + +En tu especificación de mapeo, deberías reunir esta lista de valores globales y +de reglas de mapeo en una hoja `globals` aparte, para usarla como referencia en +toda la especificación. Mira un ejemplo en la +[plantilla de especificación de mapeo](https://docs.google.com/spreadsheets/d/19sPRLP4zeFgFbtOL1wKh-rc7D0KPMu3etmOOG_x5t68/edit). + +Luego, al construir el workflow que implementa esta tabla de mapeo de valores +globales, la expresión de tu job podría parecerse a este fragmento de código. + +```js +//Workflow step 1 +//First we use fn() to transform, map & clean our data +fn(state => { + + //Global mapping rules you want to implement in your workflow + const locationMap = { + //location_id from source app: location value in destination app + 01: 'Western Cape', + 02: 'Eastern Cape', + 03: 'Gauteng' + } + + // Here we build the payload of our http request body... + // We assume the input is an array of records + const payload = state.data.map(record => ({ + location: locationMap[record.location_id], //translate location_id to the mapped value + external_id: record.case_id + })); + + return {...state, payload}; +}); + +//Workflow step 2 +//Then we post the payload built in the prior operation to create a record +post('/api/myEndpoint', { + headers: { + 'Content-Type': 'application/json', + }, + body: (state) => state.payload +}); +``` + +#### Gestionar variables globales y mapeos fuera de OpenFn {#managing-global-variables--mappings-outside-of-openfn} + +La plantilla de mapeo de OpenFn, basada en XLS, es útil para definir los +requisitos de mapeo junto con otras personas involucradas. Pero, una vez +definidas esas especificaciones, podrías considerar guardar las reglas de mapeo +`globals` en una aplicación externa, en lugar de escribirlas directamente en el +código de tu job (como en el ejemplo de arriba). + +En su lugar, podrías guardar estas variables globales y reglas de mapeo en una +tabla de base de datos aparte o en una aplicación como +[Open Concept Lab](https://openconceptlab.org/), que tiene una aplicación web +fácil de usar para registrar diccionarios de datos y reglas de mapeo, y es +compatible con API REST. Así podrías consultar estas reglas de mapeo +dinámicamente con OpenFn, para que tu integración use siempre las +especificaciones más recientes. + +En ese caso, la configuración de tu workflow podría verse como la de abajo: el +segundo step del workflow se dedica a consultar la lista de mapeos globales en +la aplicación donde están guardados, para obtener los valores globales más +recientes cada vez que se ejecuta el workflow. + +![ejemplo de workflow con OCL](/img/workflow-ocl-example.webp) + +:::tip + +Para ver la documentación de una implementación de workflow que usa +[Open Concept Lab](https://openconceptlab.org/) para guardar especificaciones de +mapeo y variables globales, +[consulta este](https://docs.google.com/presentation/d/1NEhgHD3P9luYYsJFMfGee8eR8xA03MpgcZcGPpzTLjE/edit#slide=id.g1ed42eefbd1_0_0) +resumen de Médecins Sans Frontières sobre un workflow de OpenFn de ejemplo que +mapea datos de OpenMRS a DHIS2. + +::: + +### Mapear a entidades individuales o agregadas {#mapping-to-individual-or-aggregate-entities} + +Piensa si tu integración necesita un intercambio 1 a 1 de registros individuales +o si hay que resumir o agregar los registros individuales. Puede que tu workflow +tenga que mapear entidades individuales (es decir, un mapeo 1 a 1). Por ejemplo, +puedes mapear un paciente de KoboToolbox a un paciente en DHIS2. Para esos +casos, deberías usar la +[plantilla de mapeo predeterminada de OpenFn](https://docs.google.com/spreadsheets/d/19sPRLP4zeFgFbtOL1wKh-rc7D0KPMu3etmOOG_x5t68/edit#gid=1275153608). + +En cambio, si tu workflow tiene que mapear entidades individuales a una entidad +agregada o resumida (es decir, un mapeo de muchos a 1), puedes empezar con la +[plantilla de mapeo agregado](https://docs.google.com/spreadsheets/d/1JVcM7FEkCeezHXONRaAaEPFks9lS8xO_q51jql_hUtc/edit) +de OpenFn. Por ejemplo, podrías recolectar registros individuales de pacientes +en KoboToolbox, pero querer enviar a DHIS2 un conteo agregado de pacientes para +reportar los resultados de indicadores clave (por ejemplo, la cantidad de +pacientes menores de 18 años). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/design/workflow-specs.md b/i18n/es/docusaurus-plugin-content-docs/current/design/workflow-specs.md new file mode 100644 index 000000000000..6d367e126d03 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/design/workflow-specs.md @@ -0,0 +1,34 @@ +--- +sidebar_label: Especificaciones del workflow +title: Escribir especificaciones de automatización de workflows +translation_source_hash: 3ec9ce462bdeae5af34808d835d0dd1f05d23397 +translation_review_status: machine +--- + +# Escribir especificaciones para soluciones de automatización de workflows {#writing-specifications-for-workflow-automation-solutions} + +**Los resultados clave del proceso de diseño son:** + +1. [Diagrama funcional del workflow](/design/discovery.md#workflow-requirements-gathering) +2. [Diagrama técnico del workflow](/design/discovery.md#workflow-requirements-gathering) +3. [Diagrama de arquitectura de la solución](/design/discovery.md#documenting-the-solution-architecture) +4. [Especificaciones de mapeo de elementos de datos](/design/mapping-specs.md) + +Con todo esto, tendrás lo necesario para cerrar las especificaciones del +workflow y pasárselas a los desarrolladores para que escriban los jobs. + +Cada "tarea" o "paso" del carril de OpenFn en tu diagrama técnico se puede +implementar como una operación distinta en la configuración del workflow. En el +diagrama de ejemplo de abajo, podrías implementar 1 job con 3 operaciones +encadenadas, o 3 jobs con 1 operación cada uno. + +![workflow](/img/workflow_specs.webp) + +**Las especificaciones del workflow deberían enlazar a todos los artefactos de +diseño y destacar lo siguiente:** + +1. La cantidad de jobs de OpenFn necesarios y la función de cada uno +2. Enlaces a ejemplos de entrada y salida y a la documentación de la API +3. Los identificadores únicos +4. Los volúmenes de datos esperados +5. Los requisitos de autenticación diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-help/support.md b/i18n/es/docusaurus-plugin-content-docs/current/get-help/support.md new file mode 100644 index 000000000000..600da31eac9f --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-help/support.md @@ -0,0 +1,31 @@ +--- +title: Soporte para implementaciones de OpenFn +sidebar_label: Obtener ayuda +translation_source_hash: 431c5780f38a380cc95ed50cc70855db919b12c2 +translation_review_status: machine +--- + +## ¡Pregunta a la comunidad! {#ask-the-community} + +Si necesitas ayuda para empezar, tienes preguntas o quieres darnos comentarios +sobre el producto, visita primero nuestra +**[comunidad](https://community.openfn.org)**. Nuestro equipo principal y otros +implementadores de OpenFn siguen todas las publicaciones para ayudarse entre sí, +compartir ejemplos y difundir novedades del producto. + +## ¿Tienes una pregunta sobre tu proyecto en OpenFn.org? {#have-a-question-about-your-project-on-openfnorg} + +Si usas la plataforma SaaS alojada de OpenFn y tienes una pregunta privada sobre +tu proyecto, tu cuenta o la facturación, contacta a nuestro equipo principal en +[support@openfn.org](mailto:support@openfn.org). + +## ¿Necesitas una mano? {#need-helping-hands} + +El equipo principal de OpenFn y nuestros socios certificados ofrecen soporte +empresarial, servicios de implementación y desarrollo, y capacitación para que +tu equipo arranque con fuerza. Visita nuestro sitio web: + +- Sobre los [servicios y precios de OpenFn](https://www.openfn.org/pricing) +- Sobre nuestros [socios certificados](https://www.openfn.org/partners) + + diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/glossary.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/glossary.md new file mode 100644 index 000000000000..f6e6bb0a6ae0 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/glossary.md @@ -0,0 +1,180 @@ +--- +sidebar_label: Glosario +title: Glosario de integración de datos +translation_source_hash: 69f9fa591948fe129c1d21ef35db73bdd3674110 +translation_review_status: machine +--- + +Este glosario reúne algunos de los conceptos y términos fundamentales que se +usan al hablar de integración de datos y de automatización de flujos de trabajo. + +No necesitas saber qué significan estas palabras antes de leer nuestra +documentación para usar OpenFn. Pero algunas de las tareas más importantes del +recorrido con OpenFn dan por hecho que entiendes, al menos a grandes rasgos, +cada uno de estos términos. + +Este glosario no es específico de OpenFn. El resto de la documentación y la +[página de conceptos clave](/documentation/get-started/terminology) te ayudan a +hacerte una idea de las partes de OpenFn, de cómo las llamamos y por qué. Este +glosario, en cambio, es un requisito previo a todo eso, pensado para quienes no +tienen experiencia en este ámbito. + +:::tip ¿Falta algo? + +Si encontraste una palabra, una expresión o un concepto que crees que falta en +esta página, abre un issue en [OpenFn/docs](https://github.com/OpenFn/docs), +sugiere un cambio en +[esta página](https://github.com/OpenFn/docs/blob/main/docs/get-started/glossary.md) +o pregunta en la [comunidad](https://community.openfn.org) + +::: + +## API + +API es la sigla de "application programming interface" (interfaz de programación +de aplicaciones). Es la parte de un software (la aplicación) que decidió +hacerse visible (la interfaz) a usuarios externos a la propia aplicación. +Y lo hace de forma programática, es decir, de una forma que permite a los +desarrolladores de otras aplicaciones o sistemas de datos usarla siempre de la +misma manera. + +## Protocolo de API {#api-protocol} + +No hay una regla fija sobre cómo se desarrolla una API, pero con el tiempo han +surgido estándares que hacen que la mayoría de las aplicaciones usen uno de unos +pocos formatos. Así, a un usuario nuevo le resulta más sencillo interactuar con +la API de la plataforma X. Eso es un protocolo de API. Algunos de los nombres +más conocidos son REST, SOAP, JSON y GraphQL. En lugar de reinventar la rueda, +[aquí tienes una buena introducción a en qué se diferencian los protocolos, sus formatos de datos y por qué todo eso importa.](https://frontend-digest.com/beginners-guide-to-apis-protocols-and-data-formats-f80cf7f30425) + +## Base de datos {#database} + +Casi cualquier colección organizada de datos puede llamarse base de datos. Si +tiene una estructura con la que hacer referencia a todo lo que almacena, y lo +que almacena son datos, entonces es una base de datos. + +## Integración de datos {#data-integration} + +El proceso de combinar datos de distintas fuentes en una vista centralizada. La +integración de datos es una forma de lograr la automatización de flujos de +trabajo. Sus tareas pueden simplificarse, automatizarse y gestionarse con una +herramienta de automatización de flujos de trabajo. + +## Fuente de datos {#data-source} + +Una fuente de datos es una aplicación, una base de datos o una tabla que +proporciona datos a otra plataforma. Nada es siempre una fuente de datos. +Por ejemplo, Google Sheets puede ser una fuente de datos, pero también puede +obtener datos de otras fuentes (cargas individuales de archivos CSV o datos que +los usuarios ingresan a mano). Solo la llamamos fuente cuando está +proporcionando datos a otro lugar. En el tiempo, las fuentes de datos son el +punto de partida de cualquier integración. + +## Sistema de datos {#data-system} + +A veces se confunde la diferencia entre una base de datos, una fuente de datos, +una aplicación y un sistema de datos. Un sistema de datos es un conjunto +más complejo de esas otras cosas, normalmente uno que permite a un usuario +interactuar más fácilmente con todos los datos a los que debería tener acceso. +El sistema de datos suele servir como punto de entrada a la multitud de bases de +datos, aplicaciones, tablas, etc., que de otro modo el usuario tendría que +buscar en 12 lugares distintos. + +## Cifrado {#encryption} + +Hoy en día, la seguridad lo es todo. El cifrado es el proceso de tomar algo que +cualquiera puede leer y hacer que solo puedan leerlo las personas que queremos. +OpenFn garantiza que tus datos estén cifrados en todo momento mientras están en +nuestra plataforma. +[Para saber más sobre los distintos tipos de cifrado, puedes consultar aquí.](https://ssd.eff.org/en/node/36) + +## Sistema de archivos {#file-system} + +Un sistema de archivos es a los archivos lo que un sistema de datos es a los +datos. Organiza tus archivos de forma que te resulte fácil recuperarlos de +manera estandarizada (piensa en el sistema de archivos de la computadora de tu +casa, con sus rutas de archivo). Los sistemas de archivos también existen en +otros contextos, y a veces necesitas acceder a ellos para recuperar un archivo +(un documento de Word, un CSV o un archivo de texto plano, entre otros, según tu +caso de uso). La única diferencia real entre los sistemas de archivos y los +sistemas de datos o las bases de datos es el tipo de información que almacenan: +datos frente a archivos. + +## ETL + +ETL son las siglas en inglés de "extract, transform, and load" (extraer, +transformar y cargar). A menudo se consideran las tres partes que componen una +integración de datos. Primero, extraemos (enviamos o recuperamos datos de una +fuente de datos). Después, transformamos (hacemos los cambios necesarios en los +datos para que el sistema o la aplicación de destino los acepte). Por último, +cargamos (los enviamos al destino). + +## Plataforma de integración {#integration-platform} + +Una plataforma de integración (por ejemplo, OpenFn) es una aplicación (o un +conjunto de aplicaciones) que ayuda a las organizaciones a configurar, ejecutar +y mantener o gestionar las integraciones entre todos sus sistemas. + +### iPaaS + +Puede que también veas la sigla "iPaaS". Significa "integration platform as a +service" (plataforma de integración como servicio) y es un tipo de "software +como servicio" (o "SaaS"). El SaaS es un modelo de compra de software en el que +el software se paga solo a medida que se usa (a menudo mes a mes), en lugar de +comprarse por adelantado o regalarse. + +## Metadatos {#metadata} + +Son datos que nos dicen algo sobre nuestros datos. En una tabla, por ejemplo, +son los nombres de las columnas, el número de filas, etc. Los metadatos suelen +salir en las conversaciones sobre privacidad. Por ejemplo, los reguladores +pueden querer asegurarse de que _solo los metadatos_ pasen del Ministerio A al +Ministerio B, y no la información de identificación personal (PII) de las +propias personas. + +## Push, pull y streaming {#push-pull-and-streaming} + +El push (envío) se produce cuando una acción en la fuente de datos hace +que esta envíe datos al destino. El pull (recuperación) es lo contrario: +el sistema de destino le pide los datos a la fuente a partir de alguna acción, +en lugar de esperar a que la fuente los envíe por su cuenta. El streaming +es algo distinto: se da cuando una fuente de datos envía datos a un sistema de +destino de forma prácticamente constante. + +## Webhook + +Un [webhook](/documentation/build/triggers#webhook-event-triggers) (también +llamado web callback o API HTTP push, ¡gracias, +[SendGrid](https://sendgrid.com/blog/whats-webhook/)!) es una función de una +aplicación que permite hacer push. Suele configurarse para avisar a una +URL externa cuando ocurre un evento. Un administrador de sistemas podría crear +un "webhook" que avise a una plataforma de integración cada vez que ocurra algún +evento, para que la iPaaS empiece a ejecutar un workflow complejo. + +## Datos estructurados y no estructurados {#structured-and-unstructured-data} + +Los datos estructurados son datos que tienen metadatos. Los datos no +estructurados tienen muy pocos metadatos (aunque probablemente conserven cosas +como la fecha de creación o de actualización). Sin metadatos sobre su formato, +es más difícil trabajar con los datos no estructurados mediante programación. +Para hacer bien un ETL sobre datos no estructurados necesitamos otro tipo de +reglas. Los datos estructurados son un punto de partida más fácil, porque +sabemos qué esperar de una columna con nombre, tipo de dato, tamaño de campo, +etc. + +## Flujo de trabajo {#workflow} + +El conjunto de instrucciones que determinan cómo resolver un problema o realizar +una tarea. A menudo se divide en tareas más pequeñas e independientes. + +## Automatización de flujos de trabajo {#workflow-automation} + +El uso de software para realizar tareas o un proceso de negocio de forma +autónoma, de acuerdo con reglas de negocio predefinidas y sin necesidad de +intervención humana. + +## Writeback + +Se refiere a que un sistema de destino haga un cambio en una fuente de datos. +Cuando tu aplicación de destino recibe información de una fuente de datos y +quiere hacer algo en la fuente como respuesta, eso es writeback. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/home.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/home.md new file mode 100644 index 000000000000..cbc24cce4553 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/home.md @@ -0,0 +1,151 @@ +--- +title: ¿Qué es OpenFn? +id: home +sidebar_label: ¿Qué es OpenFn? +slug: / +translation_source_hash: 97ba6c3bda9542d0d78cd1b33c23496cd233e174 +translation_review_status: machine +--- + +**OpenFn es el principal +[bien público digital](https://digitalpublicgoods.net/digital-public-goods/) +para la automatización de flujos de trabajo**. + +Es una plataforma que más de 70 ONG y ministerios de gobierno han usado para +automatizar e integrar procesos de negocio y sistemas de información críticos. + +**Conecta cualquier aplicación** con la biblioteca de [adaptors](/adaptors/) (es +decir, conectores) de código abierto de OpenFn. Desde los servicios de última +milla hasta los informes a nivel nacional, OpenFn mejora la eficiencia y la +eficacia, y hace posible una interoperabilidad segura, estable y escalable en +todos los niveles. + +Puedes desplegar OpenFn localmente o usar la segura +[plataforma alojada en la nube](https://openfn.org/pricing). Consulta la +[documentación de despliegue](/documentation/deploy/options) para saber más +sobre las opciones y los requisitos de despliegue. + +Para apoyar a quienes implementan, OpenFn cuenta con una +[comunidad](https://community.openfn.org) en línea, documentación y +[soporte](mailto:support@openfn.org). Escribe a +[partnerships@openfn.org](mailto:partnerships@openfn.org) para conocer a los +socios de implementación de OpenFn y el Programa de Socios de OpenFn. + +:::tip Automatización, integración e interoperabilidad + +OpenFn es software de código abierto que facilita a los gobiernos y las ONG +_conectar_ las distintas tecnologías que usan, automatizar procesos de negocio +críticos y escalar sus intervenciones. OpenFn hace posible la automatización, la +integración y la interoperabilidad de datos para las organizaciones de mayor +impacto del mundo. + +::: + +## Nuestros productos {#our-products} + +OpenFn ofrece un conjunto de productos totalmente interoperables entre sí, así +que nuestros usuarios pueden pasar libremente de uno a otro o usarlos todos. + +Todos los productos de OpenFn, salvo la iPaaS OpenFn v1, forman parte del +`OpenFn Integration Toolkit`, libre y de código abierto, que es un **bien +público digital** (un "DPG", por sus siglas en inglés) reconocido en el +[registro de DPG](https://digitalpublicgoods.net/registry/) y en la +[Global Goods Guidebook](https://digitalsquare.org/resourcesrepository/global-goods-guidebook) +de Digital Square. + +Los productos principales de OpenFn son: + +- **[OpenFn/lightning](https://github.com/OpenFn/lightning)**: nuestra + plataforma de código abierto de integración de datos y automatización de + flujos de trabajo. Es la versión "v2", la que se usa actualmente. +- OpenFn/platform: la primera versión de nuestra plataforma. Reemplazada por la + v2, su retiro está previsto para 2025 +- [**OpenFn/adaptors**](https://github.com/OpenFn/adaptors): código fuente de + los adaptors +- [**OpenFn/kit**](https://github.com/OpenFn/kit): CLI, herramientas para + desarrolladores y entornos de ejecución de Javascript +- [**OpenFn/docs**](https://github.com/OpenFn/docs): documentación y código + fuente de docs.openfn.org + +Consulta todos los productos y el código en +[GitHub.com/OpenFn](https://github.com/OpenFn). + +### OpenFn v2: Lightning ⚡ + +Cuando oigas "OpenFn", piensa en +[OpenFn/lightning](https://github.com/OpenFn/lightning/). La v2 es una +aplicación web de automatización de flujos de trabajo _totalmente de código +abierto_ que puede desplegarse y ejecutarse en cualquier lugar. Está pensada +para gobiernos y ONG que buscan capacidades de vanguardia en automatización de +flujos de trabajo e integración e interoperabilidad de datos, con gestión de +usuarios y auditoría completas, en una plataforma gestionada _o_ totalmente +autoalojada. + +La versión 2 aprovecha la misma tecnología central, probada y confiable, que +OpenFn v1, e incluye una interfaz visual mejorada para crear integraciones. + +![Canvas de un workflow de OpenFn](/img/case_referral_workflow.webp) + +**Mira la lista de reproducción +[OpenFn v2 Basics](https://www.youtube.com/watch?v=U0MXYRXkDnI&list=PL1pD3-abjHJ0L01RjouO2xOWKtEUYi8e4&ab_channel=OpenFn.org)** +en Youtube, con videos que te ayudarán a empezar rápido, o consulta las demás +páginas de la documentación. + +:::info OpenFn v2 reemplaza a v1 + +OpenFn v2 está disponible para cualquier usuario nuevo. Todas las organizaciones +que usan actualmente la plataforma heredada OpenFn v1 se migrarán a OpenFn v2 +antes de finales de 2024. + +::: + +### OpenFn v1 + +OpenFn v1 es la _plataforma de integración como servicio_ o "iPaaS" heredada de +OpenFn, lanzada en 2015. OpenFn v1 era open-core, con una aplicación web +propietaria. + +La plataforma v1 se retirará en 2025 y la reemplazará OpenFn v2, totalmente de +código abierto (ver arriba). + +### Herramientas para desarrolladores de OpenFn {#openfn-developer-tooling} + +[OpenFn/kit](https://github.com/OpenFn/kit) ofrece una CLI y un conjunto de +herramientas para desarrolladores con las que puedes escribir y probar +workflows, gestionar proyectos de OpenFn y desarrollar +[adaptors](https://github.com/openfn/adaptors). + +:::note Explora todo el código de OpenFn + +Puedes consultar la documentación técnica y el código fuente de las herramientas +de integración y los adaptors de OpenFn, totalmente libres y de código abierto +("FOSS"), en sus repositorios en [GitHub.com/OpenFn](https://github.com/openfn), +o consultar la sección [Despliegue](/documentation/deploy/options) para ver un +resumen de las opciones FOSS y más documentación. + +::: + +## Comunidad {#community} + +Para hacer preguntas, reportar problemas o aprender de otras personas que +implementan OpenFn, visita nuestro foro de Discourse en +[community.openfn.org](https://community.openfn.org). Regístrate y únete a la +conversación. Suele ser la forma más rápida de obtener ayuda si tienes preguntas +que no se responden aquí. + +Si tienes preguntas sobre nuestros productos, pregunta en la comunidad o escribe +al equipo principal a [support@openfn.org](mailto:support@openfn.org). + +## ¿Quién lo desarrolla? {#who-is-it-built-by} + +El principal responsable de OpenFn es +[Open Function Group](https://openfn.org/about), un equipo global de +especialistas en automatización de flujos de trabajo e integración de datos, y +colaboradores principales de OpenFn. Conoce más sobre la gobernanza de OpenFn +[aquí](https://github.com/OpenFn/governance). + +El [bien público digital](https://app.digitalpublicgoods.net/a/11038) OpenFn ha +sido creado por y para la creciente comunidad de ONG, gobiernos, socios de +"tecnología para el bien" y colaboradores de código abierto que trabajan en +intervenciones de salud y humanitarias en países de ingresos bajos y medianos +(LMIC, por sus siglas en inglés). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/implementation-checklist.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/implementation-checklist.md new file mode 100644 index 000000000000..dccbd0cd03c7 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/implementation-checklist.md @@ -0,0 +1,127 @@ +--- +sidebar_label: Lista de verificación de implementación +title: Lista de verificación de implementación para planificar tu próximo proyecto de integración +translation_source_hash: 2776efe8363e6ba09f194f27ccf75b6531694f33 +translation_review_status: machine +--- + +# Lista de verificación de implementación {#implementation-checklist} + +Esta +[lista de verificación de implementación](https://docs.google.com/spreadsheets/d/1_XY0nx0OLNUsogrIHnRaSTyZ-KdcSXks-tqwm3ZfMc4/edit#gid=72612093) +se basa en nuestra experiencia en proyectos de interoperabilidad con organismos +gubernamentales de distintos países (como oficinas de país de UNICEF, el +Ministerio de Servicios Sociales de Camboya y el Ministerio de Salud de +Tailandia). Ofrece una guía de implementación y planificación con los hitos +clave de la mayoría de los proyectos de interoperabilidad e integración. + +Aunque conviene adaptar esta lista de verificación a cada implementación, las +tareas que describe forman un plan de trabajo modelo que puede ayudar a +cualquier organización a prepararse para su próxima implementación. **El proceso +de implementación se divide en las siete fases que se resumen a continuación. +Consulta la lista de verificación para ver los pasos en detalle.** + +:::tip + +Mira un ejemplo real: el repositorio de UNICEF Camboya documenta los resultados +de esta lista de verificación en un proyecto de interoperabilidad implementado +para el Ministerio de Asuntos Sociales, Veteranos y Rehabilitación Juvenil de +Camboya y ONG asociadas: +[openfn.github.io/unicef-cambodia/](https://openfn.github.io/unicef-cambodia/) + +::: + +## (1) Preparación de la implementación {#1-preparing-for-the-implementation} + +Sienta las bases del proyecto: crea un plan de proyecto, define los roles y las +responsabilidades, documenta el valor de negocio de la implementación y confirma +su viabilidad técnica. + +Resultados clave: + +- Evaluación del valor de negocio +- Requisitos generales de los workflows +- Evaluación de viabilidad técnica +- Evaluación de capacidades + +## (2) Descubrimiento y diseño: requisitos funcionales de los workflows {#2-discovery--design---functional-workflow-requirements} + +Recopila y documenta las historias de usuario y los requisitos funcionales de +los workflows. + +Resultados clave: + +- Diagrama de arquitectura de la solución +- Diagramas de workflows (funcionales) +- Especificaciones de mapeo de elementos de datos (funcionales) + +## (3) Descubrimiento y diseño: especificaciones técnicas {#3-discovery--design---technical-specifications} + +Itera sobre los requisitos de los workflows para definir las especificaciones +técnicas de cómo se implementará cada workflow. Por ejemplo, define a qué +endpoints de la API concretos hay que acceder y qué métodos u operaciones HTTP +usar en cada uno. + +Resultados clave: + +- Diagrama de arquitectura de la solución +- Diagramas de workflows (técnicos) +- Especificaciones de mapeo de elementos de datos (técnicas) + +## (4) Desarrollo {#4-build} + +Configura el workflow en OpenFn.org y desarrolla y prueba los jobs y adaptors +que se usarán en él. + +Resultados clave: + +- Configuración del proyecto de OpenFn +- Jobs +- Adaptors nuevos o actualizados (si hacen falta) +- Borrador de la "Project Security Configuration Checklist" para documentar los + ajustes de configuración implementados + +## (5) Pruebas {#5-testing} + +Crea un conjunto de pruebas y realiza las pruebas de aceptación de usuario +(UAT). Después, incorpora los comentarios recibidos e itera sobre el proceso de +pruebas. + +Resultados clave: + +- Conjunto de pruebas completo +- Lista de nuevas solicitudes pendientes (si los comentarios identifican + necesidades para fases futuras) +- "Project Security Configuration Checklist" completa + +## (6) Capacitación y preparación para la puesta en marcha {#6-training--prep-for-go-live} + +Capacita a los administradores de OpenFn y a los usuarios finales de los +sistemas de destino, y documenta lo que se implementó. En esta fase también se +migran la configuración y el código a los entornos de producción. + +Resultados clave: + +- Documentación publicada +- Grabación en video de la capacitación +- "Project Security Configuration Checklist" aprobada +- Proyecto de OpenFn listo para usar + +## (7) Despliegue y soporte {#7-rollout--support} + +"Activa" los workflows de OpenFn para la puesta en marcha y establece +estructuras de soporte y un modelo de gobernanza para la gestión del cambio. + +Resultados clave: + +- Proyecto de OpenFn "en producción" +- Modelo de soporte documentado + +## ¿Preguntas o comentarios? {#questions-or-feedback} + +Si tienes aportes, comentarios o preguntas, ¡contribuye! Envía una pull request +a esta página de documentación en GitHub o deja un comentario en la +[OpenFn Community](https://community.openfn.org/). + +¿Te interesa recibir **capacitación sobre el proceso de implementación de +OpenFn**? Contacta a [partnerships@openfn.org](mailto:partnerships@openfn.org). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/security-compliance.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/security-compliance.md new file mode 100644 index 000000000000..f851002fb156 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/security-compliance.md @@ -0,0 +1,140 @@ +--- +sidebar_label: Seguridad y cumplimiento +title: Seguridad y cumplimiento +translation_source_hash: e7615c14f8ea9167b2580775204ed61653b87ce7 +translation_review_status: machine +--- + +# Todo sobre S³ {#all-about-s} + +En OpenFn damos prioridad a soluciones **seguras**, **estables** y +**escalables** (**"S³"**, por sus iniciales en inglés, es nuestro lema), en ese +orden. Protegemos tus datos, mantenemos las integraciones en funcionamiento y +crecemos junto a tu organización. Gobiernos y ONG de todo el mundo confían en +nosotros. + +✓ Configuración segura por defecto en la plataforma para proteger tus datos y +reducir al mínimo las brechas de seguridad + +✓ Ajustes de seguridad sólidos y configurables para garantizar el cumplimiento +de tus políticas + +✓ Crea canalizaciones de datos de "persistencia cero" para controlar por +completo dónde se almacenan los datos + +✓ Capacitación y orientación sobre la implementación de la seguridad para los +equipos de tus proyectos ([más información](/get-started/security.md)) + +Consulta nuestro sitio web principal para saber más sobre +[seguridad y confianza](https://www.openfn.org/trust) y +[cumplimiento](https://www.openfn.org/compliance) en OpenFn. + +## Cumplimiento {#compliance} + +Las implementaciones de OpenFn son muy configurables y pueden desplegarse en +cualquier lugar, lo que ayuda a garantizar el cumplimiento de las políticas de +privacidad y seguridad de datos de tu país o de tu organización. + +**Para saber más sobre cómo entendemos el cumplimiento, sobre todo de normas +como el GDPR o la HIPAA, consulta nuestra página web de +[cumplimiento](https://www.openfn.org/compliance).** Contacta a +[nuestro equipo principal](mailto:support@openfn.org) si te interesa recibir +consultoría y asesoramiento sobre cómo desplegar y configurar tu implementación +de OpenFn para cumplir al 100 % con las normas. + +## OpenFn y el almacenamiento de datos {#openfn-and-data-storage} + +En tu ecosistema digital, **OpenFn suele funcionar como una solución de +procesamiento y transferencia de datos, no como un servicio de almacenamiento de +datos.** + +Como bien público digital de código abierto, OpenFn puede desplegarse en +cualquier lugar ([ver documentación](/deploy/options.md)), y los workflows +pueden configurarse para respetar los acuerdos de intercambio de datos y las +políticas de seguridad de tu organización. + +Consulta las páginas de documentación de +[gestión de proyectos](/manage-projects/platform-mgmt.md) para saber más sobre +los ajustes de proyecto y de +[almacenamiento de datos](/manage-projects/io-data-storage.md). + +El siguiente diagrama muestra una arquitectura de ejemplo en la que incluso +OpenFn Cloud puede configurarse como una **canalización de datos de +"persistencia cero"** para cumplir los requisitos de seguridad y residencia de +datos. Así, los socios pueden configurar y poner a prueba proyectos rápidamente +con la plataforma de OpenFn alojada en la nube, lista para usar, y migrar a un +despliegue local cuando estén preparados para escalar. + +![Arquitectura de ejemplo](/img/zero-persistence.webp) + +Para borrar los datos de tu proyecto en cualquier momento, puedes +[borrar tu proyecto](/manage-projects/platform-mgmt.md) o +[borrar tu cuenta](/manage-users/user-profile.md). + +## Cifrado {#encryption} + +OpenFn Cloud almacena los datos en un producto Cloud SQL orientado a la +seguridad, que garantiza cifrado de 256 bits en reposo, y solo permitimos +conexiones con TLS/SSL. + +Cifrado de la plataforma: + +- Advanced Encryption Standard de 256 bits +- Cifrado SSL/TLS en tránsito +- Credenciales y secretos cifrados en disco + +Más información en [openfn.org/trust](https://www.openfn.org/trust#encryption). + +## Credenciales {#credentials} + +Las [credenciales](/manage-projects/manage-credentials.md), que dan a OpenFn +acceso a las API de tus distintas tecnologías, están cifradas en reposo. Así, en +el improbable caso de una brecha en la base de datos, un atacante no podría leer +tu información de autenticación sin acceder a varios servidores protegidos de +forma independiente. + +Las conexiones con tus aplicaciones de destino solo se hacen por HTTPS, con SSL +y, en la mayoría de los casos, autenticación básica. Las especificaciones +técnicas de la conexión dependen del endpoint REST de la aplicación a la que te +conectas. Encontrarás la documentación técnica de cada adaptor en la +[documentación de adaptors](/adaptors) o en su repositorio en Github, en +[github.com/OpenFn/adaptors](https://github.com/OpenFn/adaptors). + +Solo tú (quien las creó) puedes ver las credenciales, que se cargan en tu +entorno de ejecución privado para ejecutar los jobs. Puedes borrarlas en +cualquier momento y se eliminarán del sistema. +[Consulta la documentación](/manage-users/user-credentials.md) para saber más +sobre la gestión y el uso compartido de credenciales en OpenFn. + +## Gestión del acceso de usuarios y RBAC {#user-access-management-and-rbac} + +OpenFn permite gestionar el acceso de los usuarios mediante **control de acceso +basado en roles (RBAC)**, con el que los administradores asignan permisos +detallados tanto a nivel de entorno como de proyecto. Los roles (por ejemplo, +Admin, Editor y Viewer) controlan quién puede ver, editar, ejecutar o gestionar +workflows y credenciales. El acceso puede limitarse a proyectos o +configuraciones de entorno concretos, con registros de auditoría y tokens de API +de alcance limitado para garantizar la seguridad y el cumplimiento. + +Cuando invitas a nuevos usuarios a trabajar en tu proyecto como colaboradores, +se les asigna un rol que determina sus permisos. Consulta la documentación sobre +[colaboración](/manage-projects/collaboration.md) y +[roles de usuario](/manage-projects/user-roles-permissions.md) para más +información. + +Cuando los usuarios se registran en la plataforma, se les pide que creen una +contraseña segura. Los superadministradores de OpenFn también pueden activar la +[autenticación multifactor](/manage-users/user-profile.md), la caducidad de las +contraseñas y el bloqueo de cuentas inactivas. + +:::info ¿Más preguntas sobre la seguridad de OpenFn? + +Primero, consulta las páginas de [confianza](https://www.openfn.org/trust) y +[cumplimiento](https://www.openfn.org/compliance) de nuestro sitio web, así como +la [guía de implementación segura](/get-started/security.md). + +Haz tus preguntas en la [comunidad](https://community.openfn.org/) o +[contacta a nuestro equipo principal](mailto:security@openfn.org) para consultas +privadas. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/security.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/security.md new file mode 100644 index 000000000000..4142c40e2f13 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/security.md @@ -0,0 +1,122 @@ +--- +sidebar_label: Seguridad en las implementaciones +title: Consideraciones de seguridad para proyectos de integración de datos +translation_source_hash: 366084aa20c5ce7de23ca63331268d1b40fca41a +translation_review_status: machine +--- + +# Directrices de seguridad para implementaciones de integración de datos {#security-guidelines-for-data-integration-implementations} + +Aunque las tecnologías de tu solución de integración puedan considerarse +seguras, la integración de datos sigue teniendo muchos riesgos de seguridad, +sobre todo durante la implementación. Por eso, con el apoyo de Digital Square, +elaboramos una **Guía de seguridad para implementaciones de integración de +datos**. + +Desde 2014, en Open Function Group (los principales responsables de OpenFn) +hemos ayudado a implementar casi 100 soluciones de integración de datos para más +de 45 ONG y gobiernos de todo el mundo. Gracias a nuestro trabajo con los +equipos de seguridad de distintos socios, a nuestra propia investigación y +desarrollo, a las consultas con expertos en seguridad internos y externos, y a +las alianzas con otras comunidades de práctica, conocemos bien las buenas +prácticas y las consideraciones de seguridad para proyectos de integración de +datos, y queremos compartirlas con toda la comunidad del desarrollo digital. + +**Esta guía busca ayudar a quienes implementan soluciones digitales en las +comunidades de bienes públicos digitales y de bienes globales a entender mejor +los riesgos de seguridad, y presenta 23 buenas prácticas para las distintas +fases de implementación de los proyectos de integración de datos.** También +enlaza a algunos recursos de código abierto de OFG que nuestro equipo usa en su +propio proceso de implementación de proyectos de OpenFn. + +Más abajo en esta página encontrarás la lista completa de las 23 buenas +prácticas. + +**Para acceder a la guía, consulta las diapositivas de abajo o haz clic en el +enlace para compartirla y descargarla:** +[https://bit.ly/security_guidebook](https://bit.ly/security_guidebook) + +

+ +

Integración de datos segura: 23 buenas prácticas de implementación

+

Principios básicos

+
    +
  1. Conoce las políticas pertinentes sobre el intercambio, el almacenamiento y la protección de datos
  2. +
  3. Extrae y transfiere solo los datos imprescindibles
  4. +
  5. Documenta, documenta, documenta
  6. +
+

Analizar y planificar

+
    +
  1. No des por sentada la seguridad de las API
  2. +
  3. Reserva tiempo para las pruebas de seguridad
  4. +
+ +

Diseñar

+
    +
  1. Recurso: plantilla de especificación de mapeo
  2. +
  3. Recurso: diagrama de flujo de datos de la arquitectura
  4. +
  5. Recurso: Project Security Configuration & Go-Live Checklist (lista de verificación de configuración de seguridad y puesta en marcha del proyecto)
  6. +
  7. Ten en cuenta la idempotencia, los identificadores únicos y las operaciones de tipo "upsert" para garantizar la integridad de los datos
  8. +
  9. Diseña pensando en los fallos y en el reprocesamiento de transacciones
  10. +
  11. Ten en cuenta la validación de datos
  12. +
+

Crear

+
    +
  1. Usa el seguimiento de cambios y el control de versiones
  2. +
  3. Cifra siempre que puedas
  4. +
  5. Usa una autenticación sólida; no hables con desconocidos
  6. +
  7. Usa ámbitos de autorización para limitar el acceso
  8. +
  9. Registra las transacciones para supervisar la actividad y controla qué información se registra
  10. +
+

Desplegar

+
    +
  1. Vuelve a probar, sobre todo las credenciales, antes del despliegue
  2. +
  3. Capacita a los usuarios y a los administradores de sistemas en la seguridad de la integración
  4. +
  5. Revisa de nuevo tus requisitos de seguridad antes de la puesta en marcha
  6. +
  7. Define los puntos de contacto para reportar problemas de seguridad
  8. +
+

Supervisión y gestión continuas

+
    +
  1. Considera modelos de gobernanza para la gestión continua y los cambios de requisitos
  2. +
  3. Capacita a los socios en la gestión del cambio
  4. +
  5. Ten una estrategia de gestión del acceso
  6. +
+ +Sigue leyendo para conocer otros recursos y comunidades de implementadores que +te pueden interesar. + +### Recursos citados en la guía {#resources-referenced-in-the-guidebook} + +- [Principles of Digital Development Privacy and Security Guide](https://digitalprinciples.org/wp-content/uploads/PDD_Principle-AddressPrivacySecurity_v2.pdf) +- [UNICEF policy on personal data protection](https://www.unicef.org/supply/media/5356/file/Policy-on-personal-data-protection-July2020.pdf.pdf) +- [International Committee of the Red Cross Handbook on data protection in humanitarian action](https://www.icrc.org/en/data-protection-humanitarian-action-handbook) +- [GDPR Quick Guide](https://gdpr.eu/what-is-gdpr/) +- [Sanity.io A Rough Guide to Running a GDPR Compliant SaaS Business](https://www.sanity.io/blog/a-rough-guide-to-running-a-gdpr-compliant-saas-business) +- [OWASP API Security Project](https://owasp.org/www-project-api-security/) +- [GovStack Security & API Standards](https://www.govstack.global/wp-content/uploads/2021/08/Security_Building_Block_Definition_1.0.1.pdf) +- [Health Data Governance Principles](https://www.healthdataprinciples.org/) +- [CDC Health Data Privacy, Confidentiality, and Security Guidelines](https://gicsandbox.org/sandbox-cms/health-data-privacy-confidentiality-and-security-guidelines-development-toolkit#dd01fcf80d4d46f08a099b282bc23f16) + +### Recursos de OpenFn {#openfn-resources} + +Encontrarás más orientación sobre implementación en todo este sitio de +documentación. Si usas OpenFn, puedes saber más sobre su seguridad y +cumplimiento normativo en [openfn.org/trust](http://openfn.org/trust) y +[openfn.org/compliance](http://openfn.org/compliance). + +Estas son las principales plantillas y recursos de OpenFn citados en la guía: + +- [Plantilla de especificación de mapeo](https://docs.google.com/spreadsheets/d/1IqTIgOzyOztEevXbgY_4uE8Y8tiHXufZXx-IyJZase0/edit#gid=1822444315) +- [Diagrama de arquitectura de la solución](https://lucid.app/lucidchart/1e997197-2d67-4393-8394-a532d83561b2/edit#?templateid=fb96ae05-e288-4d1f-b3fc-2cbf7641a7cc) +- [Recursos de diagramas BPMN](/documentation/design/design-workflow#diagram-using-global-standards) +- [Project Security Configuration & Go-Live Checklist](https://docs.google.com/document/d/1CbQkN7SqNmXeqt3nMTYP4ioQlTuwF2LbDkkFqhp0zsU/edit?usp=sharing) + +### Comunidades de práctica y otros expertos {#communities-of-practice--other-experts} + +Estas son otras comunidades que puedes seguir para obtener más orientación sobre +seguridad. + +1. [OpenHIE Privacy & Security Working Group](https://wiki.ohie.org/display/resources/Privacy+and+Security+Working+Group+Call) +2. [GovStack](https://www.govstack.global/) +3. [DHIS2 Security Team & Community of Practice](https://dhis2.org/security/) +4. [Asia eHealth Information Network (AeHIN) Communities of Practice](https://www.asiaehealthinformationnetwork.org/communities-of-practice/) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/standards.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/standards.md new file mode 100644 index 000000000000..10d73a559b7b --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/standards.md @@ -0,0 +1,235 @@ +--- +sidebar_label: Estándares +title: Estándares y OpenFn +translation_source_hash: 4268e67655414b5830b8194dd7e98e23d283a613 +translation_review_status: machine +--- + +OpenFn sigue estándares globales para el software de código abierto y para las +soluciones de motor de workflows. Sigue leyendo para saber cómo cumple OpenFn +con estándares concretos. + +## Bien público digital {#digital-public-good} + +La [Digital Public Goods Alliance](https://digitalpublicgoods.net/) reconoce a +OpenFn como bien público digital, o "DPG" por sus siglas en inglés. + +:::info Definición de bien público digital + +Software de código abierto, datos abiertos, modelos de IA abiertos, estándares +abiertos y contenido abierto que respetan la privacidad y otras buenas prácticas +aplicables, no causan daño por diseño y son muy relevantes para alcanzar los +Objetivos de Desarrollo Sostenible (ODS) de la Agenda 2030 de las Naciones +Unidas + +::: + +Puedes leer más sobre el estándar DPG +[aquí](https://digitalpublicgoods.net/standard/). + +## Bien global para la salud {#global-good-for-health} + +OpenFn es una de las 36 aplicaciones de software que Digital Square reconoce +como +[Global Good for Health](https://wiki.digitalsquare.io/index.php/What_are_Global_Goods#:~:text=Digital%20Square%20Global%20Goods%20are,scale%2C%20are%20used%20across%20multiple). + +:::info Definición de bien global para la salud + +Un bien global de software de salud digital maduro es software libre y de código +abierto (FOSS), cuenta con el respaldo de una comunidad sólida, tiene una +estructura de gobernanza clara, se financia con varias fuentes, se ha desplegado +a una escala considerable, se usa en varios países, ha demostrado su eficacia, +está diseñado para ser interoperable y es una aplicación estándar emergente. + +::: + +Puedes leer más sobre los bienes globales para la salud +[aquí](https://digitalsquare.org/digital-health-global-goods). + +## Arquitectura estándar de OpenHIE {#openhie-standard-architecture} + +OpenFn se considera una tecnología de referencia de OpenHIE y cumple con la +arquitectura estándar de OpenHIE para implementaciones de salud digital. + +_Esta sección da por hecho que conoces la especificación de OpenHIE, un marco de +referencia que hace posible compartir datos de salud entre sistemas de +información mediante un intercambio de información de salud ("HIE", por sus +siglas en inglés). Para saber más, consulta la +[documentación de OpenHIE](https://guides.ohie.org/arch-spec/) y su +[comunidad](https://ohie.org/)._ + +### OpenFn y OpenHIE {#openfn-and-openhie} + +La plataforma OpenFn v2 ([OpenFn/lightning](https://github.com/OpenFn/)) es un +**_motor de workflows_** compatible con OpenHIE que se usa para (1) automatizar +procesos de negocio complejos que abarcan varios sistemas digitales (incluidos +los componentes de OpenHIE _y_ los sistemas del punto de atención) y (2) +gestionar el mapeo y la transformación de datos. + +Si tu organización implementa la arquitectura estándar de OpenHIE, OpenFn te +ofrece un motor de workflows que se comunica con tu capa de interoperabilidad +("IOL", por sus siglas en inglés). Con OpenFn puedes automatizar: + +- workflows entre sistemas del punto de servicio; +- workflows entre los componentes centrales del HIE; +- los pasos de transformación necesarios para preparar los datos antes de + enviarlos a otros componentes del HIE a través de la IOL. (Los workflows de + OpenFn son una alternativa a los "mediators" de OpenHIM que se puede usar y + gestionar desde una interfaz web). + +OpenFn cumple los +[requisitos funcionales](https://guides.ohie.org/arch-spec/openhie-component-specifications-1/openhie-interoperability-layer-iol#openhie-iol-functional-requirements) +de la IOL de OpenHIE, por lo que algunas organizaciones también usan OpenFn como +su capa de interoperabilidad central. Aun así, OpenFn todavía no puede usarse +como una **_capa de interoperabilidad_** totalmente compatible con OpenHIE, +porque no usa el perfil IHE ATNA (consulta el +[requisito IOL-WF1](https://guides.ohie.org/arch-spec/openhie-component-specifications-1/openhie-interoperability-layer-iol#openhie-iol-workflow-requirements)). + +![Arquitectura de OpenHIE](/img/openhie_architecture.webp) + +_Para ver un resumen de OpenFn Lightning y de cómo encaja en OpenHIE, mira +nuestra +[presentación para el OpenHIE showcase](https://www.youtube.com/watch?v=PTRRZBYtqyc)_ +o sigue leyendo para tener más contexto. + +### Más sobre cómo OpenFn cumple la especificación de OpenHIE {#more-on-how-openfn-supports-the-openhie-spec} + +#### La capa de interoperabilidad (IOL): {#the-interoperability-layer-iol} + +- Se sitúa entre los componentes de OpenHIE y los sistemas del punto de atención +- Sirve como punto de entrada único y pasarela segura a OpenHIE +- Cumple los requisitos de enrutamiento y auditoría de transacciones + +_OpenFn Lightning cumple los requisitos funcionales de la IOL, pero no es +totalmente compatible con OpenHIE, porque todavía no usa el perfil IHE ATNA_ + +#### El motor de workflows: {#the-workflow-engine} + +- Ofrece interfaces listas para usar que se conectan a los sistemas del punto de + atención +- Gestiona el mapeo y la transformación de datos complejos para darles el + formato que necesita el sistema de destino (por ejemplo, mapear datos de un + sistema del punto de atención al modelo de datos de un componente de OpenHIE, + mapear datos no FHIR a perfiles FHIR, o ambas cosas) +- Envía los datos a la capa de interoperabilidad +- Puede seguir el estado de la atención de un paciente a lo largo del tiempo y + actuar según ese contexto (por ejemplo, enviando alertas) para mejorar su + atención. + +_OpenFn Lightning es un motor de workflows compatible con OpenHIE_ + +### Caso práctico: OpenFn como mediator de OpenHIM {#case-study-openfn-as-an-openhim-mediator} + +En Nigeria, como parte del +[proyecto ALMANACH](https://articles.nigeriahealthwatch.com/almanach-revolutionising-the-management-of-childhood-illnesses-in-adamawa-state/), +SwissTPH usó OpenFn para automatizar el mapeo y el intercambio de datos entre +CommCare y DHIS2 para la vigilancia de enfermedades. El workflow funcionó +durante varios años en la nube de OpenFn. Para preparar el traspaso y la +ampliación de escala, el equipo de SwissTPH preparó después una integración +profunda con OpenHIM para un despliegue local. + +SwissTPH incorporó su workflow de OpenFn a su instancia de OpenHIM como +"mediator", de modo que todos los datos pasaran por esta IOL. Al mismo tiempo, +siguió aprovechando el adaptor de DHIS2 de OpenFn, listo para usar, y las +plantillas de workflow reutilizables para desarrollar rápidamente una +automatización que da formato a los datos recibidos de CommCare y los mapea al +modelo de datos de DHIS2. + +![SwissTPH](/img/swisstph.webp) + +## GovStack + +OpenFn cumple la +[especificación estándar de GovStack](https://govstack.gitbook.io/bb-workflow/2-description) +para motores de workflows. + +## Principios para el Desarrollo Digital {#principles-for-digital-development} + +OpenFn se diseñó para el sector social y ha dado prioridad activamente a los +[Principios para el Desarrollo Digital](https://digitalprinciples.org/) desde +sus inicios. + +Las soluciones de OpenFn: + +- **son interoperables** (conectan cualquier aplicación); +- **son reutilizables** (usa configuraciones de OpenFn existentes como + plantillas, o comparte, copia y modifica fácilmente tus propias + configuraciones; consulta docs.openfn.org/library); +- **son sostenibles** (opciones de implementación flexibles, sin dependencia de + un proveedor); +- **son escalables** (OpenFn usa tecnología de nivel empresarial para gestionar + grandes volúmenes de datos y ofrece varias opciones de despliegue para que la + solución sea totalmente tuya en cualquier servidor); +- **promueven los estándares abiertos y el acceso abierto** (mediante nuestro + software de código abierto, la documentación y funciones que ayudan a los + usuarios a implementar estándares abiertos en sus soluciones de intercambio de + información), y +- **abordan la privacidad y la seguridad**. + +## FHIR para el intercambio de datos de salud {#fhir-for-health-data-exchange} + +[FHIR](https://www.hl7.org/fhir/) (se pronuncia "fire", fuego en inglés 🔥) es +un estándar para el intercambio de datos de salud, publicado por HL7®. + +Las organizaciones de salud usan OpenFn para conectar varios sistemas, +compatibles con FHIR o no, de forma segura, estable y escalable. OpenFn puede +facilitar 2 categorías de workflows FHIR: + +### 1. Intercambio de datos de no FHIR a FHIR {#1-non-fhir-to-fhir-data-exchange} + +Los usuarios de OpenFn pueden configurar workflows que convierten datos no FHIR +a formatos compatibles con FHIR y luego los envían a sistemas FHIR. + +Por ejemplo, obtener datos de la aplicación móvil CommCare, convertirlos a FHIR +y enviarlos al almacén FHIR del sistema nacional de salud. +![Workflow de no FHIR a FHIR](/img/workflow_nonfhir_fhir.webp) + +### 2. Intercambio de datos de FHIR a FHIR {#2-fhir-to-fhir-data-exchange} + +Los usuarios de OpenFn también pueden configurar workflows para automatizar el +intercambio y el enrutamiento de datos _ya_ compatibles con FHIR hacia otros +sistemas compatibles con FHIR. + +Por ejemplo, obtener datos de la API FHIR de OpenMRS y reenviarlos al almacén +FHIR del sistema nacional de salud (sin necesidad de transformar los datos). + +![Workflow de FHIR a FHIR](/img/workflow_fhir_fhir.webp) + +## Adaptors de FHIR {#fhir-adaptors} + +Los [adaptors](/adaptors) de OpenFn agilizan la configuración de integraciones +con las aplicaciones de destino (¡incluidos los endpoints FHIR!). Actualmente, +el equipo principal trabaja en un conjunto de adaptors específicos de FHIR para +hacer posible la interoperabilidad con sistemas FHIR. + +El [adaptor fhir-4](/adaptors/fhir-4) facilita el acceso y la modificación de +datos alojados en cualquier servidor compatible con +[FHIR r4](https://www.hl7.org/fhir/R4/). También ofrece asistencia de código +completa a los desarrolladores mientras crean definiciones de recursos +concretas, lo que simplifica la introducción de datos y la lógica de mapeo. + +También ofrecemos un adaptor genérico, [fhir](/adaptors/fhir), compatible con +todas las versiones de FHIR. + +:::info Compatibilidad con FHIR 4 + +El adaptor `fhir-4` es nuevo en OpenFn desde marzo de 2025. Ofrece un nivel de +compatibilidad más completo que el adaptor genérico [fhir](/adaptors/fhir). La +compatibilidad con otras versiones de FHIR llegará pronto + +::: + +Consulta la +[wiki de adaptors](https://github.com/OpenFn/adaptors/wiki/Generating-Fhir-Adaptors) +para aprender a crear tu propio adaptor de FHIR específico para la guía de +implementación de FHIR que uses + +## Otros estándares de datos {#other-data-standards} + +Los workflows de OpenFn pueden automatizar reglas de transformación, limpieza y +formato de datos para garantizar el cumplimiento de los estándares específicos +de _tu_ organización. + +Pregunta en la [comunidad](https://community.openfn.org) cómo puede usarse +OpenFn para ayudar a automatizar la aplicación y el cumplimiento de otros +estándares de datos. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/terminology.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/terminology.md new file mode 100644 index 000000000000..188a3b86b969 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/terminology.md @@ -0,0 +1,310 @@ +--- +title: Conceptos clave +translation_source_hash: 2885a84bfad099adf8223a97458e5d5dbe03e5b4 +translation_review_status: machine +--- + +En todo el OpenFn Integration Toolkit y en este sitio de documentación +encontrarás términos propios de OpenFn que es importante entender. Esta página +es tu guía de referencia: un glosario de las palabras _propias de OpenFn_ más +importantes y de lo que significan. + +:::tip ¿Falta algo? + +Si encontraste una palabra, una expresión o un concepto que crees que falta en +esta página, abre un issue en [OpenFn/docs](https://github.com/OpenFn/docs), +sugiere un cambio en +[esta página](https://github.com/OpenFn/docs/blob/main/docs/get-started/terminology.md) +o pregunta en la [comunidad](https://community.openfn.org) + +::: + +Si buscas un glosario de términos genéricos de integración de datos (y no de +estos términos _propios de OpenFn_), ve a la página +[Glosario de integración](/documentation/get-started/glossary) de la sección +Primeros pasos. Si no, ¡sigue leyendo! + +## Proyecto {#project} + +Un proyecto es una agrupación administrativa en OpenFn, parecida a un "espacio +de trabajo". + +En la plataforma (OpenFn/lightning), los proyectos definen quién puede acceder a +la configuración y al historial de tus workflows de OpenFn. Cada proyecto tiene +un propietario y uno o más colaboradores. + +En el despliegue y el desarrollo locales, un proyecto también corresponde a un +archivo [`project.yaml`](/documentation/deploy/portability-versions#v2), que +define su configuración. + +En ambos casos, un proyecto contiene workflows, triggers, credenciales y todo lo +que necesitas para automatizar e integrar con OpenFn. + +## Workflow + +:::tip + +¡Los workflows son la parte de **"qué hacer"** de la automatización! + +::: + +Un workflow es una secuencia estructurada de tareas, procesos o acciones que se +ejecutan automáticamente según reglas, disparadores y lógica predefinidos. + +Al trabajar con IA, los workflows aportan la ejecución estructurada que hace +falta para convertir lo que propone un LLM en acciones reales, mientras que los +agentes de IA permiten tomar decisiones más dinámicas dentro de los workflows. + +Un workflow es un conjunto formado por un trigger, steps, paths y lógica +personalizada, conectados entre sí para automatizar un proceso de negocio o una +tarea concretos. Se configura en el Canvas de la aplicación web, o localmente +con código. + +La automatización de OpenFn gira en torno a los +[workflows](/documentation/build/workflows), que pueden tener uno o varios +steps. Un workflow puede ejecutarse en tiempo real (a partir de un evento, por +ejemplo, el registro de un paciente nuevo), de forma programada (por ejemplo, +todos los días a las 8 a. m.) o manualmente, cuando lo necesites. + +Piensa en un workflow como un conjunto de instrucciones que le darías a alguien +de tu equipo. Por ejemplo: crea un registro de paciente nuevo en OpenMRS cuando +llegue desde CommCare un formulario con un cliente recién registrado; exporta +datos a DHIS2 todas las semanas, los viernes a las 11 p. m.; envía un SMS con el +número de confirmación del pago cuando llegue el mensaje de confirmación del +pago. + +Los workflows más comunes automatizan: + +- Reportes para un seguimiento de programas mejor y más rápido (sobre todo + reportes de dispositivos móviles a un MIS) +- Pasos rutinarios de ETL (extracción, transformación y carga) y de limpieza de + datos +- Alertas (SMS, correo electrónico) +- Derivaciones entre sistemas de socios +- Asignaciones o aprobaciones de tareas +- Reportes de quejas o de casos +- Transacciones financieras o pagos + +:::note Los workflows son reutilizables + +Los workflows son totalmente configurables y reutilizables. También pueden +encadenarse para automatizar procesos de varios pasos y sincronizaciones de +datos en ambos sentidos, que mantienen la coherencia de los datos entre varias +aplicaciones (con patrones Saga de varias aplicaciones). + +::: + +### Adaptor + +:::tip + +¡Los adaptors son la parte de **"dónde hacerlo"** de la automatización! + +::: + +Los [adaptors](/adaptors) de OpenFn son módulos de código abierto que dan a tus +workflows las funciones que necesitan para comunicarse con la API de un sistema +concreto. Algunos ejemplos son [dhis](/adaptors/dhis2), +[`postgresql`](/adaptors/postgresql) y [`http`](/adaptors/packages/http-docs). +Actualmente hay más de 70 adaptors activos, y cualquiera puede crearlos o +mejorarlos. El código fuente está en +[GitHub/Adaptors](https://github.com/OpenFn/adaptors). + +### Credencial {#credential} + +:::tip + +¡Las credenciales son la parte de **"cómo iniciar sesión"** de la +automatización! + +::: + +Una credencial sirve para autenticarse en una aplicación de destino (por +ejemplo, el nombre de usuario, la contraseña y la URL de inicio de sesión de una +base de datos) para que un step de un workflow pueda ejecutarse. El modelo de +seguridad de OpenFn guarda las credenciales separadas de los workflows, para que +los nombres de usuario y las contraseñas almacenados (todos cifrados) no se +filtren ni lleguen a las personas equivocadas. + +## Trigger + +:::tip + +¡Los triggers son la parte de **"cuándo hacerlo"** de la automatización! + +::: + +Un [trigger](/documentation/build/triggers) determina **cómo y cuándo** deben +ejecutarse automáticamente los workflows (por ejemplo, en tiempo real o de forma +programada). Al activarse, el trigger crea una nueva +[work order](/documentation/get-started/terminology#work-order) y ejecuta el +workflow. + +Configura un trigger de tipo "Webhook Event" si quieres que tu workflow se +ejecute en tiempo real cuando ocurre un evento en una aplicación externa (por +ejemplo, cuando se envía un formulario nuevo o llega una notificación nueva). + +Configura un trigger de tipo "Cron" si quieres que tu workflow se ejecute según +un calendario concreto (por ejemplo, todos los días a las 8 a. m., o el primer +lunes de cada mes). + +## Work order + +:::tip + +Las work orders registran **"cuándo y qué disparó"** la automatización, y nos +ayudan a ver si el workflow se completa correctamente y cuándo. + +::: + +Una work order es una solicitud para ejecutar un workflow con una entrada +determinada (por ejemplo, un formulario recién enviado o un registro de paciente +que hay que procesar). + +Se crea una work order cada vez que se activa el trigger de un workflow, o +cuando un usuario administrador la crea manualmente. + +Para completarse correctamente, la work order debe llegar sin errores a un step +final; así se garantiza que el procesamiento terminó. Puede que hagan falta +varios "runs" del workflow para que una work order se considere correcta. + +Las work orders permiten seguir de cerca si un workflow procesa correctamente +cada entrada (por ejemplo, "registro de paciente 123"), como en una auditoría de +"gestión de casos". + +Imagina que un workflow está configurado para crear un paciente nuevo en OpenMRS +cada vez que se abre un caso nuevo en CommCare. Si durante la semana siguiente +se abren 5 casos en CommCare, verás 5 work orders distintas para este workflow. +Si 4 work orders son correctas y una falla, verás 4 pacientes nuevos en OpenMRS, +y el administrador de tu sistema habrá recibido un aviso de que no se pudo crear +uno de esos pacientes (o se pondrá en marcha el manejo de errores más robusto +que hayas configurado). + +![Work order](/img/work_order_shot.webp) + +:::note + +Normalmente hay una correspondencia de 1 a 1 entre las work orders y las cosas +reales con las que trabajas. Por ejemplo, podrías crear un workflow que obtenga +de DHIS2 todos los datos de eventos actualizados de las últimas 2 semanas y los +publique en un mapa público con CartoDB. Este workflow se disparará a intervalos +definidos, en este caso cada 2 semanas, así que al cabo de un mes solo verás 2 +work orders en OpenFn (una cada dos semanas). Cada work order tendrá un estado +correcto o fallido, con runs relacionados que registran los detalles de cada +transacción y cuántos registros de eventos se procesaron. + +::: + +## Run + +:::tip + +¡Los runs registran **"lo que pasó"** en la automatización! + +::: + +Un run es un intento individual de ejecución para completar una work order. +Puede haber varios runs de un workflow para una misma work order (porque el +primer run puede fallar y hay que reintentarlo para procesarla correctamente). + +Los runs tienen hora de inicio, hora de fin, logs y códigos de estado que +indican cuándo tuvieron lugar, qué hicieron y si tuvieron éxito o no. + +![Canvas de workflow de OpenFn](/img/run_view_logs.webp) + +Imagina que un workflow está configurado para crear un paciente nuevo en OpenMRS +cada vez que se abre un caso nuevo en CommCare. Si hoy se crea 1 paciente: + +- Se creará 1 work order en OpenFn, que disparará un run para crear el paciente + en OpenMRS. +- Si ese run falla por un error (por ejemplo, la contraseña del usuario de + OpenMRS es incorrecta o al paciente le falta información obligatoria), el + "Status" de ese run y de la work order relacionada aparecerá como `failed`. +- Los usuarios de OpenFn pueden corregir el error y elegir "rerun" para volver a + ejecutar ese run fallido. Esto crea un 2.º run relacionado con la work order + original. Si tiene éxito, el "Status" del 2.º run y de la work order aparecerá + como "success". + +### Logs + +Los logs son los registros que genera el motor de ejecución de workflows para +dejar constancia de lo que se hizo al ejecutar un workflow o un step concreto. + +Los desarrolladores de OpenFn pueden controlar lo que aparece en los logs +editando las sentencias `console.log(...)` en las expresiones de job de cada +step. + +![Logs](/img/logs_run.webp) + +## History + +En la plataforma, la página History muestra una lista de todas las work orders y +todos los runs que se procesaron en un proyecto. + +![History](/img/case-referral-history.webp) + +## Inspector + +En la plataforma, el Inspector es la interfaz que permite editar, probar y +ejecutar workflows. + +El Inspector tiene 3 paneles clave: `Input`, `Editor` y `Output`. + +![Inspector](/img/inspector_interfaces.webp) + +### Input + +El Input son los datos (`json`) que un step de un workflow usa como entrada +inicial cuando se ejecuta. Cada run tiene un Input (el `state` inicial) y un +Output (el `state` final). + +Los Inputs pueden crearse automáticamente a partir de un evento de webhook (por +ejemplo, un mensaje reenviado o un payload JSON enviado a OpenFn) o de otro step +del workflow, o manualmente, por un usuario de OpenFn. + +Ejemplo de Input a partir de un formulario enviado desde una aplicación móvil de +recolección de datos (como Kobo, ODK o CommCare): + +```json +{ + "data": { + "form": { + "@name": "Register New Patient", + "case": { + "@case_id": "a9bX12c", + "@date_modified": "2021-01-21T07:08:19.431000Z", + "@user_id": "aaa", + "@xmlns": "http://commcarehq.org/case/transaction/v2", + "create": { + "case_name": "John Doe", + "age": 16, + "case_type": "patient", + "owner_id": "alan.worker" + } + } + } + } +} +``` + +### Output + +El Output son los datos finales (`json`) que produce un step de un workflow, +según la lógica de negocio definida en su expresión de job. El Output pasa al +siguiente step del workflow, a la aplicación de destino conectada, o a ambos. + +Ejemplo de Output si el formulario del ejemplo anterior se mapeara a una +aplicación de gestión de casos conectada: + +```json +{ + "data": { + "patient": { + "full_name": "John Doe", + "age_at_enrollment": 16, + "type": "new", + "source": "mobile-app" + } + } +} +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/get-started/try-out.md b/i18n/es/docusaurus-plugin-content-docs/current/get-started/try-out.md new file mode 100644 index 000000000000..f446638383c7 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/get-started/try-out.md @@ -0,0 +1,60 @@ +--- +title: Prueba la v2⚡ +id: try-out +sidebar_label: Prueba la v2⚡ +translation_source_hash: 4e28f15f405f837836a724296995294f1761e78d +translation_review_status: machine +--- + +Si te interesa probar OpenFn v2⚡ hoy mismo, tienes 3 opciones: + +## 1. Regístrate para obtener una cuenta gratuita {#1-register-for-a-free-account} + +Regístrate para obtener una cuenta gratuita en el servicio alojado de OpenFn.org +y crea tu propio proyecto privado. Para hacerlo, visita: +[www.openfn.org/register](https://www.openfn.org/register) + +Ten en cuenta que esta cuenta gratuita tiene límites. Mejora tu plan para +acceder a más funciones en la plataforma alojada y segura de OpenFn. Más +información en [nuestro sitio web](https://www.openfn.org/pricing). + +:::tip ¿Ya tienes una cuenta? + +Visita [www.openfn.org/login](https://www.openfn.org/login) para iniciar sesión +en tu cuenta de Lightning v2. Si solo tienes un usuario de v1, tendrás que crear +una cuenta nueva de v2 en +[www.openfn.org/register](https://www.openfn.org/register). + +::: + +## 2. Inicia sesión en el sitio de demostración de OpenFn {#2-log-into-the-openfn-demo-site} + +Visita [demo.openfn.org](https://demo.openfn.org) e inicia sesión con estas +credenciales para explorar la plataforma: + +- nombre de usuario: `demo@openfn.org` +- contraseña: `welcome12345` + +:::warning + +El sitio de demostración se restablece cada 24 horas y se pierde cualquier +cambio de configuración que hagas. No lo uses para ninguna configuración que +quieras conservar. + +::: + +## 3. Instala OpenFn/lightning localmente {#3-install-openfnlightning-locally} + +Instala OpenFn v2 localmente para acceder al software de código abierto y +explorar sin límites. La documentación para desarrolladores está en nuestro +repositorio de GitHub: +[github.com/OpenFn/lightning](https://github.com/OpenFn/lightning). + +:::info ¿Tienes preguntas? + +Consulta esta documentación para ver más detalles sobre funciones concretas +(mira el menú lateral), explora la +[página principal de la documentación](/get-started/home.md) o publica tus +preguntas en la [comunidad](https://community.openfn.org). + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/hosted/overview.md b/i18n/es/docusaurus-plugin-content-docs/current/hosted/overview.md new file mode 100644 index 000000000000..d88e2b8fdda5 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/hosted/overview.md @@ -0,0 +1,173 @@ +--- +title: Gestión de facturación y suscripciones en OpenFn.org +id: overview +sidebar_label: Suscripciones (OpenFn.org) +translation_source_hash: d1a4431aa8938f214a646b619a889fa8e0467f9b +translation_review_status: machine +--- + +## Resumen {#overview} + +[OpenFn.org](https://www.openfn.org) ofrece un despliegue de OpenFn seguro, +estable, escalable y alojado en la nube, como una opción SaaS lista para usar y +bajo demanda que puede resultar más rentable que gestionar tu propio despliegue +local. + +Ve a **[openfn.org/pricing](https://www.openfn.org/pricing)** para conocer +nuestros planes y servicios, o sigue leyendo para saber cómo gestionar las +cuentas de facturación y las suscripciones. + +:::tip ¿Necesitas OpenFn en _tus_ servidores? + +OpenFn se puede desplegar en cualquier lugar. Consulta la +[documentación de despliegue](/deploy/options.md) para conocer las opciones +compatibles y la vía de despliegue local "hazlo tú mismo". Si buscas ayuda +experta para gestionar tu despliegue local, consulta los +[servicios de despliegue gestionado](https://www.openfn.org/pricing?hostingType=selfHosted) +que ofrece el equipo principal de OpenFn. + +::: + +## Registro de usuarios {#user-registration} + +Cuando creas una cuenta de usuario en la nube desde OpenFn.org/signup, obtienes +acceso inmediato a un único proyecto del plan gratuito, y creamos una cuenta de +facturación personal (consulta [más abajo](#billing-accounts)) que luego puedes +usar para comprar project spaces adicionales o mejorar tu plan gratuito. + +## Cuentas de facturación {#billing-accounts} + +Todos los proyectos _pertenecen_ a una única cuenta de facturación. La cuenta de +facturación contiene: + +- **Suscripciones de proyectos** (tus proyectos, cada uno con su suscripción); +- **Métodos de pago** (tarjetas de crédito y métodos de pago por factura); y +- **Usuarios de la cuenta de facturación** (los usuarios de OpenFn que pueden + ver o gestionar esta cuenta de facturación) + +### Usuarios de la cuenta de facturación {#billing-account-users} + +De forma predeterminada, eres el propietario de tu propia cuenta de facturación +personal. Puedes invitar a otros **administradores** para que agreguen o +modifiquen métodos de pago y cambien la suscripción de un proyecto. También +puedes agregar **lectores**, que pueden ver las suscripciones de proyectos de tu +cuenta, los métodos de pago y los demás usuarios de la cuenta de facturación, +pero no pueden modificar nada de eso. + +## Gestionar suscripciones y métodos de pago {#manage-subscriptions-and-payment-methods} + +Sigue leyendo o mira el siguiente video para saber cómo gestionar tus +suscripciones y métodos de pago. + + + +### Métodos de pago {#payment-methods} + +Para mejorar la suscripción de tu proyecto, necesitas un método de pago +aprobado. Se admiten métodos de pago por "factura" y con "tarjeta de crédito". + +Para gestionar tus métodos de pago, haz clic en el menú `Billing Account` (si +estás en la lista de proyectos o en la página de perfil de usuario) o en el menú +`Subscription` (si estás en la página del proyecto o del workflow). Desde ahí, +haz clic en `Payment Methods` en el menú lateral. + +#### Pago por factura {#invoice-payment} + +Cuando agregas un método de pago por factura, tienes que esperar a que el equipo +de OpenFn.org lo apruebe antes de poder usarlo. + +:::tip Agregar un método de pago por factura + +Para agregar un método de pago por factura, haz clic en el botón "Add a new +invoice method" de la página de métodos de pago. Se abrirá un formulario que +puedes completar y enviar. Cuando hayas enviado el formulario correctamente, tu +solicitud de pago por factura aparecerá en la lista marcada como `pending`. + +Alguien del equipo de facturación de OpenFn se pondrá en contacto contigo para +verificar la información antes de aprobar el método de pago. + +_Ten en cuenta que solo puedes tener UN método de pago por factura pendiente a +la vez._ + +::: + +#### Pago con tarjeta de crédito {#credit-card-payment} + +Cuando agregas una tarjeta de crédito como método de pago, Stripe.com verifica +los datos de la tarjeta de inmediato y puedes mejorar tus suscripciones en ese +mismo momento. + +:::tip Agregar una tarjeta de crédito como método de pago + +Para agregar una tarjeta de crédito como método de pago, haz clic en el botón +"Add a new card" de la página de métodos de pago. Se abrirá un formulario en el +que puedes completar los datos de tu tarjeta. Stripe.com verificará tu tarjeta +de inmediato y podrás usarla para mejorar tu suscripción o crear una nueva +suscripción de proyecto. + +Si no consigues agregar tu tarjeta, contáctanos en support@openfn.org e indica +el error (si lo hay). + +::: + +### Planes y límites {#plans--limits} + +La lista completa de planes y límites está disponible en +[openfn.org/pricing](https://www.openfn.org/pricing). + +### Mejorar tu suscripción {#upgrading-your-subscription} + +:::warning Necesitas un método de pago válido + +Asegúrate de tener un método de pago válido antes de intentar mejorar tu +suscripción. Consulta la sección [Métodos de pago](#payment-methods) para más +detalles. + +::: + +Para mejorar tu suscripción: + +1. Haz clic en "Subscription" en el panel de tu proyecto +2. Haz clic en "Manage Subscription" +3. Selecciona un plan de la lista y baja para agregar runs adicionales si los + runs incluidos en tu plan no alcanzan para tu proyecto. _(Te recomendamos + usar el control deslizante para fijar el valor)_ +4. Selecciona un método de pago para pagar la mejora. +5. Baja hasta el final de la página para revisar los cambios en tu suscripción. +6. Haz clic en "Update subscription" + +Al mejorar el plan, se te cobrará de inmediato la _diferencia_ entre tu plan +actual y el nuevo. Al final de tu ciclo, el siguiente cargo será solo el costo +del nuevo plan. + +### Bajar el plan de tu suscripción {#downgrading-your-subscription} + +Al bajar de plan, puedes seguir usando tu plan actual hasta que termine el +ciclo, porque ya pagaste por adelantado el uso de ese ciclo. Cuando termina el +ciclo, se aplican los límites más bajos y el siguiente cargo será por el precio +del nuevo plan. + +## Transferir suscripciones de proyectos {#transferring-project-subscriptions} + +Si eres administrador de una cuenta de facturación, puedes solicitar transferir +la titularidad de un proyecto de tu cuenta de facturación (es decir, pedirle a +otra persona que lo pague). Escribe su correo electrónico y recibirá un aviso. + +Para aceptar la solicitud de transferencia, esa persona tiene que ser +administradora de al menos una cuenta de facturación con un método de pago +activo. Elegirá la cuenta de facturación y el método de pago que quiere usar, y +entonces la transferencia se completará y será _ella_ quien pague la próxima vez +que haya que pagar esa suscripción. + +## Cómo encaja todo (para los ingenieros 🤓) {#how-it-fits-together-for-the-engineers-} + +```mermaid +erDiagram + "User" }|--|{ "Project" : "A user can access many projects" + "User" }|--|{ "Billing Account" : "A user can access many billing accounts" + "Project" ||--|| "Subscription" : "A project has one active subscription" + "Billing Account" ||--o{ "Payment Method" : "A billing account has many payment methods" + "Payment Method" ||--o{ "Subscription" : "A payment method can be used for many subscriptions" + "Billing Account" ||--o{ "Subscription" : "A billing account has many subscriptions" + "Plan" ||--o{ "Subscription" : "Many subscriptions use the same plan" +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/best-practices.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/best-practices.md new file mode 100644 index 000000000000..8a765a61d5af --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/best-practices.md @@ -0,0 +1,108 @@ +--- +sidebar_label: Buenas prácticas +title: Buenas prácticas +translation_source_hash: db347090195ee9bfd115330405094e81c8cf3cef +translation_review_status: machine +--- + +## Usar secretos de credenciales en el código del job {#referencing-credential-secrets-in-your-job-code} + +Si tienes que usar secretos de credenciales en el código del job, puedes mapear +claves desde tu `state.configuration`. El ejemplo de abajo mapea de forma +dinámica el usuario y la contraseña de tu `state.configuration` (o de la +"credencial", si usas la app) al cuerpo de tu solicitud HTTP. + +```js +post('/api/v1/auth/login', { + body: { + username: $.configuration.username, //map the UN from credential + password: $.configuration.password, //map the PW from credential + }, + headers: { 'content-type': 'application/json' }, +}); +``` + +> **Nota:** Aunque la mayoría de los adaptors manejan la autenticación de forma +> automática, el adaptor `@openfn/language-common` permite manejarla a mano. + +**Enfoque recomendado:** en lugar de acceder a las credenciales desde el código +del job, deberías: + +1. Usar un step con un adaptor específico (por ejemplo, `@openfn/language-http`) + que tenga su propia credencial para la autenticación. +2. Agregar después un step con `@openfn/language-common` si necesitas + transformar más los datos. + +:::info OpenFn elimina la configuración y las funciones del state final + +OpenFn elimina automáticamente la clave `configuration` y cualquier función de +tu state final, y también de los logs si ejecutas workflows en la app. Así ayuda +a mantener seguros los secretos de tus credenciales y a evitar que se filtren en +History. + +::: + + + +## Manejo de errores {#error-handling} + +Si algo sale mal, normalmente lo mejor es dejar que tus jobs fallen. + +Un job que falla genera el estado correcto en la app de OpenFn y avisa de que +algo anda mal. + +No te preocupes, ¡los errores pasan todo el tiempo! Incluso los workflows más +consolidados lanzan algún error de vez en cuando por datos inesperados en algún +punto del proceso. Es parte de la vida; lo más importante es enterarse. + +Los errores deberían lanzarse desde el job sin mucha ceremonia: el runtime los +atrapa y los procesa como corresponde. + +Si un job lanza un error, este se registra en el log y se escribe en el state +final, así que debería ser fácil encontrarlo e identificar la causa. + +En un workflow, es habitual dejar que un job falle y luego hacer alguna tarea, +como enviar un correo a un administrador del sistema para avisarle del problema. + +Al procesar lotes de datos, quizás quieras atrapar los errores de cada elemento +y escribirlos en el state. Así, un elemento con problemas no arruina todo el +lote, y sabes qué elementos funcionaron y cuáles fallaron. Después puedes lanzar +una excepción para indicar que el job falló. + +## Escribir funciones que se puedan probar {#writing-testable-functions} + +Para que las funciones de un workflow de OpenFn se puedan probar, saca la lógica +real (mapeo, validación, formato, filtrado) de los bloques de operaciones y +ponla en funciones auxiliares puras que reciban entradas simples y devuelvan +salidas simples, sin depender de `state`, de variables globales ni de sistemas +externos. Exporta cada función auxiliar y deja que las operaciones solo pasen +los datos del state a esas funciones: + +```js +// workflows/patient-sync/transform.js +export function isValid(record) { + return Boolean(record.first_name && record.birth_date); +} + +export const mapPatient = record => ({ + name: `${record.first_name} ${record.last_name ?? ''}`.trim(), + dob: record.birth_date, + sex: record.gender?.toLowerCase() === 'f' ? 'female' : 'male', +}); + +fn(state => ({ + ...state, + data: state.data.filter(isValid).map(mapPatient), +})); +``` + +Para probar las funciones auxiliares exportadas, consulta +[Pruebas unitarias de jobs](/documentation/jobs/unit-testing-jobs). diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/compilation.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/compilation.md new file mode 100644 index 000000000000..a6cd033c14d3 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/compilation.md @@ -0,0 +1,53 @@ +--- +sidebar_label: Compilación +title: Compilación +translation_source_hash: 84aca9c4f6f2ddcb2a23ffb5806b86f9ce720c0d +translation_review_status: machine +--- + +## Compilación {#compilation} + +Técnicamente, el código que escribes no es JavaScript ejecutable. No puedes +simplemente ejecutarlo con Node.js. Hay que transformarlo, o compilarlo, en +código JS estándar y portable. + +:::warning + +Este es un tema avanzado, pensado sobre todo para desarrolladores de JavaScript +y para quienes sienten curiosidad técnica. Esta documentación no pretende ser +completa: solo es un empujón en la dirección correcta para ayudarte a entender +cómo funcionan los jobs. + +::: + +Las principales diferencias entre el código de OpenFn y JavaScript son: + +- Las funciones del nivel superior del código se ejecutan de forma síncrona (en + secuencia), aunque contengan código asíncrono. +- El código de OpenFn no contiene instrucciones import (aunque técnicamente + puede tenerlas). Estas se agregan al compilar. +- El código compilado es un módulo ESM de JavaScript que exporta por defecto un + array de funciones async. El runtime importa y ejecuta estas funciones. + +No debería hacer falta entender la compilación en detalle, pero deberías saber +que el código que escribes no es el código que se ejecuta. + +Si eres desarrollador de JavaScript, entender algunos de estos cambios podría +ayudarte a comprender mejor cómo funciona OpenFn. Con la CLI, puedes ejecutar +`openfn compile path/to/job.js -a ` para ver el código compilado. + +Este es un ejemplo de cómo queda un job sencillo al compilarlo: + +Este job: + +```js +get('/patients'); +``` + +Se compila en este módulo de JavaScript: + +```js +import { get } from '@openfn/language-http'; +export * from '@openfn/language-http'; +export default [get('/patients')]; +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/data-transformation.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/data-transformation.md new file mode 100644 index 000000000000..f97b4304bd9f --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/data-transformation.md @@ -0,0 +1,199 @@ +--- +sidebar_label: Transformación de datos +title: Transformación de datos +translation_source_hash: a4731145379c6ab801d158a4696762e407360d24 +translation_review_status: machine +--- + +## Mapeo de objetos {#mapping-objects} + +Un caso de uso común de `fn` en OpenFn es mapear, convertir o transformar un +objeto del sistema A al formato del sistema B. + +A menudo lo hacemos en varios jobs del mismo workflow, para poder usar distintos +adaptors. Pero en este ejemplo trabajaremos con tres operaciones en un solo job +con el adaptor http: una para obtener los datos, otra para transformarlos y otra +para subirlos: + +```js +// Fetch an object from one system +get('https://www.system-a.com/api/patients/123'); + +// Transform it +fn(state => { + // Read the data we fetched + const obj = state.data; + + // convert it by mapping properties from one object to the other + state.uploadData = { + id: obj.id, + name: `${obj.first_name} ${obj.last_name}`, + metadata: obj.user_data, + }; + + // Don't forget to return state! + return state; +}); + +// Post it elsewhere +post('https://system-b.com/api/v1/records/123', (state) => state.uploadData); +``` + +:::tip Conversiones por lotes + +Estos ejemplos muestran cómo convertir un solo objeto, pero a veces tenemos que +convertir muchos objetos a la vez. + +Consulta el ejemplo de each() más abajo para ver cómo hacerlo con el operador +each. + +También puedes usar una función map() o forEach() de JavaScript dentro de un +callback o de un bloque `fn`. + +En general, es más fácil repartir la lógica del job en muchas operaciones de +nivel superior, cada una a cargo de una tarea, que tener unas pocas operaciones +muy anidadas. + +::: + +Esto está bien. De hecho, tener muchas operaciones que hacen cada una una tarea +pequeña es una buena práctica. Hace que el código sea más legible y fácil de +probar, y también más fácil de entender y de depurar cuando algo sale mal. + +Sin embargo, todos los argumentos de una operación aceptan una función (lo que +permite referencias diferidas al state, como se describió antes), así que +podemos hacer la conversión directamente en la operación post, por ejemplo: + +```js +// Fetch an object from one system +get('https://www.system-a.com/api/patients/123'); + +// Transform and post it elsewhere +post('https://system-b.com/api/v1/records/123', state => ({ + id: state.data.id, + name: `${state.data.first_name} ${state.data.last_name}`, + metadata: state.data.user_data, +})); +``` + +Usar bien estas funciones de resolución diferida es fundamental para escribir +buenos jobs de OpenFn. + +## Iteración con each() {#iteration-with-each} + +Un caso de uso muy común en la integración de datos es convertir datos de un +formato a otro. Normalmente, esto implica recorrer un array de elementos, +convertir los valores y mapearlos a un array nuevo. + +En OpenFn, podemos usar el operador `each()` para hacerlo. + +```js +each( + '$.data.items[*]', + get(state => `/patients/${state.data.id}`) +); +``` + +El operador `each()` recibe como primer argumento una cadena de JSON path, que +apunta a alguna parte del state. En JSON path, usamos `$` para referirnos a la +raíz, la notación de punto para encadenar una ruta y `[*]` para "seleccionar" un +array de elementos. El segundo argumento es una operación, que recibe cada +elemento al final del JSON path como `state.data`, pero por lo demás recibe el +resto del objeto state. + +Así podemos recorrer cada elemento y volver a escribirlo en el state, así: + +```js +fn((state) => { + // Initialize an array into state to use later + state.transformed = [] + return state; +}) +each("$.items[*]", fn(state) => { + // Pull the next item off the state + const next = state.data; + + // Transform it + const transformed = { ...next }; + + // Write it back to the top-level transformed array on state + state.transformed.push(transformed) + + // Always return state + return state; +}) +``` + +O podemos pasarle otra operación, como en este ejemplo de Salesforce: + +```js +each( + '$.form.participants[*]', + upsert('Person__c', 'Participant_PID__c', state => ({ + Participant_PID__c: state.pid, + First_Name__c: state.participant_first_name, + Surname__c: state.participant_surname, + })) +); +``` + +Cada participante se inserta o actualiza en Salesforce (`upserted`), con sus +campos de Salesforce mapeados a los valores del array `participants`. + +:::info JSON paths + +Usar una cadena de JSON path como primer argumento de `each()` permite que el +runtime evalúe de forma diferida el valor de esa ruta. +[Consulta Leer el state de forma diferida](/jobs/operations.md#reading-state-lazily). + +No todas las operaciones admiten una cadena de JSON path. Consulta la +[documentación de cada adaptor](/adaptors) para orientarte. + +::: + +## Inicialización de variables {#variable-initialisation} + + + +Es habitual tener que declarar algunas variables al inicio del job. Pueden ser +valores estáticos para usar más adelante, funciones que se llaman varias veces a +lo largo del job o partes del state que queremos devolver al final. + +Se considera una buena práctica usar un bloque `fn()` para hacerlo al inicio del +job, creando propiedades personalizadas en el state, por ejemplo: + +```js +fn(state => { + // Create an array to hold the final results of the job + state.results = []; + + // Create a lookup index to be used during the job + state.lookup = {}; + + state.keyMap = { + AccountName: 'C__Acc_Name', // capture various static mappings for transformation + }; + + state.maxPageSize = 200; // Define some config options + + state.convertToSF = item => { + /* ... */ + }; // A function to be re-used + + return state; +}); + +// the rest of your job code goes here +get('/abc'); + +fn(state => { + /* ... */ + + // Only return the results array as output from the job + return { result: state.results }; +}); +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/image-handling.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/image-handling.md new file mode 100644 index 000000000000..2ef7e67eee2f --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/image-handling.md @@ -0,0 +1,118 @@ +--- +title: Manejo de imágenes +translation_source_hash: 0edbfa9276676e40ecf010f6dd237f915e249426 +translation_review_status: machine +--- + +Los jobs de OpenFn se ejecutan en JavaScript y, en la mayoría de los casos, +manejan datos JSON de API REST o de webhooks. Recibimos JSON, lo transformamos +con JavaScript y luego lo enviamos a otra API REST. Sin embargo, a veces +necesitas trabajar con imágenes u otros archivos binarios. En esta página te +explicamos cómo hacerlo. + +:::success En resumen: + +Las imágenes y otros archivos binarios, en general, **_simplemente +funcionan™️_**. Los casos poco comunes podrían requerir cambios en los adaptors. + +::: + +:::info Manipulación avanzada de imágenes + +¿Necesitas cambiar el tamaño de una imagen, comprimirla, quitarle o agregarle +metadatos EXIF, o leer sus metadatos? Usa el +[adaptor `image-utils`](/adaptors/packages/image-utils-docs), que ejecuta estas +operaciones de forma nativa en tu job, sin necesidad de un microservicio +externo. Consulta +[Manipulación de imágenes con el adaptor `image-utils`](#image-manipulation-with-the-image-utils-adaptor) +más abajo para ver los detalles. + +- **Sin acceso a binarios externos**: los jobs de la plataforma siguen + ejecutándose en un entorno aislado de Node.js y no pueden invocar programas + externos como `imagemagick` o `ffmpeg`. El adaptor `image-utils` funciona + completamente dentro del entorno de ejecución de Node.js, así que no los + necesita. +- **Archivos grandes**: Base64 aumenta mucho el tamaño del payload, así que + evítalo con archivos grandes siempre que puedas. Es mejor trabajar con Buffers + (el formato que devuelven por defecto las operaciones de `image-utils`). + +::: + +## Base64 (manejo estándar) {#base64-standard-handling} + +En esencia, para trabajar con imágenes, PDF u otros archivos, guardarlos en +`state` y pasarlos de un step a otro en un workflow de OpenFn, hay que +codificarlos en base64 y luego volver a convertirlos en Buffers antes de +enviarlos a la API de un sistema de destino. + +El adaptor HTTP ya tiene todo lo que necesitas para hacerlo. Consulta: + +1. [Opciones de solicitud (`parseAs`)](/adaptors/packages/http-docs#requestoptions) +2. [Codificar](/adaptors/packages/http-docs#util_encode) una cadena en formato + Base64. +3. [Decodificar](/adaptors/packages/http-docs#util_decode) una cadena codificada + en Base64 para devolverla a su formato original. + +## Compatibilidad nativa en los adaptors {#adaptor-native-support} + +Algunos adaptors (DHIS2, FHIR-4, Sunbird-RC) manejan binarios de forma integrada +en endpoints conocidos de imágenes o archivos. Cuando solicitas un archivo (una +imagen, un PDF, etc.), la respuesta se convierte automáticamente en una cadena +codificada en base64. + +## Trabajar con Buffers {#working-with-buffers} + +También puedes trabajar directamente con buffers en el código de un job de +OpenFn, con código como este: + +```js +fn(state => { + const encoded = Buffer.from(state.data.myBase64string, 'base64'); + return { ...state, encodedImage }; +}); +``` + +o bien: + +```js +fn(state => { + const decoded = state.data.myBuffer.toString('base64'); + return { ...state, decoded }; +}); +``` + +## Manipulación de imágenes con el adaptor `image-utils` {#image-manipulation-with-the-image-utils-adaptor} + +En los workflows que necesitan transformar una imagen, y no solo moverla, usa el +[adaptor `image-utils`](/adaptors/packages/image-utils-docs). Ofrece: + +```js +// resize an image to given `width`/`height` dimensions. +resize(state.data.buffer, { width: 1200, height: 1600 }); +// reduce image quality/file size until it meets a target `maxBytes`, down to a `minQuality` floor. +compress(state.data.buffer, { maxBytes: 700 * 1024, minQuality: 20 }); +// remove all EXIF metadata from an image. +stripMetadata($.data.photoBase64); +// write EXIF key-value pairs (e.g. `UserComment`) into a JPEG. +embedMetadata($.data.buffer, { UserComment: 'patient-id=42' }); +// read an image's dimensions, orientation, size, and EXIF data without modifying it. +metadata($.data.photoBase64); +``` + +Cada operación acepta una cadena Base64 o un Buffer y escribe su resultado en +`state.data` (normalmente como `buffer`; si necesitas una cadena, tienes +disponible `parseAs: 'base64'`). + +Consulta la +[documentación del adaptor `image-utils`](/adaptors/packages/image-utils-docs) +para ver todos los detalles sobre las opciones y los valores que devuelve cada +función. + +## Resumen {#summary} + +La mayoría de los casos de uso, como obtener una imagen de un sistema y subirla +a otro, deberían **_simplemente funcionar™️_**. En los workflows que necesitan +transformar la propia imagen (cambiar su tamaño, comprimirla, quitarle o +agregarle datos EXIF, o leer sus metadatos), usa el +[adaptor `image-utils`](/adaptors/packages/image-utils-docs) como se describe +más arriba. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/javascript.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/javascript.md new file mode 100644 index 000000000000..180811e2ea83 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/javascript.md @@ -0,0 +1,331 @@ +--- +title: Consejos de JavaScript +sidebar_label: Consejos de JavaScript +translation_source_hash: 7faca517b9178d0323cb188a0b80fdb54558b6ff +translation_review_status: machine +--- + +OpenFn admite todas las características modernas de JavaScript. + +Esta sección destaca algunas características y operadores útiles de JavaScript +que pueden ayudarte a escribir código más limpio. No pretende ser una guía +exhaustiva, sino una referencia a algunas buenas técnicas sobre los aspectos más +nuevos del lenguaje. + +### Usar la operación `fn(...)` del adaptor common {#using-the-fn-operation-from-the-common-adaptor} + +Te recomendamos usar la operación `fn(...)` para manipular el state y aplicar +JavaScript personalizado que transforme, manipule y limpie los datos antes de +enviarlos a las aplicaciones de destino. + +```js +fn(state => { + //call state to edit + //add your custom javascript here to manipulate state + return state; //always return state +}); +``` + +### Variables: var, let y const {#variables-var-vs-let-vs-const} + +JavaScript ofrece tres formas distintas de declarar variables, y esto puede +resultar un poco confuso. + +- `var` es una variable cuyo valor se puede reasignar. Puedes volver a declarar + una `var` varias veces. +- `let` es básicamente lo mismo que una var, pero no se puede volver a declarar + y sus reglas de alcance son un poco distintas. +- `const` se usa para variables cuyo valor no cambia. + +En realidad no importa mucho qué estilo uses (salvo, quizás, si intentas asignar +un valor a una `const`). + +De todos modos, la mayoría de los jobs de OpenFn se escriben con un estilo +bastante funcional, así que es posible que ni siquiera necesites declarar +variables. + +
+¿Qué es la programación funcional? + +La programación funcional es un estilo de programación cada vez más popular en +el JavaScript moderno. + +En términos generales, la idea es reducir al mínimo el uso de sentencias de +control de flujo (como `if/else` o `for`) y usar en su lugar cadenas de +funciones. En la programación funcional, los datos pasan por un pipeline hasta +obtener el resultado que queremos. ¿Te suena? + +```js +const items = [10, 109, 55]; + +// Imperative JS +const transformedItems = []; +for (const i of items) { + if (i < 100) { + transformedItems.push(i * 2); + } +} + +// Functional js +const transformedItems = items.filter(x => x > 100).map(x => x * 2); +``` + +La programación funcional suele ser más breve y concisa que la programación +imperativa habitual. Esto tiene su lado bueno y su lado malo, pero si estás +acostumbrado al estilo, suele ser muy legible y se traslada bien de un lenguaje +a otro. + +
+ +La mayor parte del JavaScript moderno e idiomático se escribe con `const` y +`let`. Esto puede hacer que tu código sea más legible e intencional, y las +reglas son bastante sencillas: + +- Usa `const` si no quieres que cambie el valor de una variable. +- Usa `let` si esperas que cambie el valor de una variable. + +Esto puede complicarse un poco con los objetos y los arrays. Podemos asignar un +objeto a una const y aun así cambiar las propiedades del objeto. Lo mismo pasa +con los arrays. Todo esto tiene que ver con los punteros y con cómo JavaScript +almacena las variables: la clave es que no estás asignando un valor nuevo a la +variable, sino modificando el _contenido_ de la variable. + +Mira estos ejemplos: + +```js +// Example 1: Objects +const data = {}; + +// We can mutate the object here +// The data variable is still referencing the same object +data.name = 'OpenFn'; + +data = { name: 'Lightning' }; // This throws a runtime error because we are re-assigning the variable! + +// Example 2: Arrays +const ids = [1, 2, 3]; + +// We can call functions on the ids array, which will mutate the array's contents +ids.push(4); +ids.pop(); + +// But we cannot re-assign the variable + +ids = [4, 5, 6]; // This throws a runtime error because we are re-assigning the variable! +``` + +### Encadenamiento opcional {#optional-chaining} + +JavaScript es un lenguaje sin tipos, lo cual es muy conveniente para los jobs de +OpenFn y suele facilitar las cosas. + +Sin embargo, un problema común es que, al escribir cadenas largas de +propiedades, se lanza una excepción si falta alguna propiedad. Y esto pasa todo +el tiempo al obtener datos de servidores remotos. + +El encadenamiento opcional permite que JavaScript deje de evaluar una cadena de +propiedades y devuelva undefined como resultado de toda la expresión: + +```js +const x = a.b?.c?.d?.e; +``` + +En este ejemplo, si `c`, por ejemplo, no está definida, `x` recibe el valor +`undefined`. No se lanza ninguna excepción. + +También puedes hacerlo con propiedades de tipo string, aunque la sintaxis es un +poco más engorrosa: + +```js +const x = a.b['missing-link']?.d?.e; +``` + +También sirve para llamadas opcionales a funciones (es menos útil al escribir +jobs, pero lo incluimos para que esté completo): + +```js +const x = a.b?.(); +``` + +Puedes combinar el encadenamiento opcional con el operador de **"fusión de +nulos"** (nullish coalescing), que tiene un nombre estupendo. Funciona un poco +como una expresión ternaria o como un or: si lo que está a la izquierda del +operador devuelve `null` o `undefined`, se devuelve el valor de la derecha. + +```js +const x = a.b?.c?.d?.e ?? 22; +``` + +En este ejemplo, si alguno de los valores de la cadena no está definido, `x` +recibe el valor 22. + +### Funciones flecha {#arrow-functions} + +Usamos funciones flecha en toda esta guía y suponemos que la mayoría de los +usuarios ya sabe usarlas. + +Una función flecha es otra forma de escribir una función de JavaScript. Hay +varias razones por las que son populares en el JavaScript moderno: + +- Resultan ligeras, porque requieren menos sintaxis +- No tienen un alcance `this`, aunque esto es en gran parte irrelevante para + programar en OpenFn (y, de hecho, para la mayoría de los frameworks modernos + de JS) + +Las funciones flecha son siempre anónimas (no tienen nombre), pero, por +supuesto, se pueden asignar a variables. + +```js +function upperCase(name) { + return name.toUpperCase(); +} + +const getName = () => { + return name.toUpperCase(); +}; +``` + +Una función flecha puede contener una sola expresión y ningún cuerpo, y en ese +caso devuelve esa expresión: + +```js +function getX() { + return x; +} + +const getX = () => x; +``` + +Este patrón hace que las funciones flecha sean ligeras y elegantes, y encaja muy +bien con los paradigmas de la programación funcional. + +:::tip ¿Problemas para devolver un objeto? + +Encierra siempre los objetos entre paréntesis cuando devuelvas un objeto desde +una función flecha: + +``` +post('wwww', () => ({ id: 'a', name: 'adam' })) +``` + +Cuando JavaScript ve una llave `{` después de una flecha, espera un bloque de +sentencias, no un objeto. Encerrar el objeto entre paréntesis le indica a +JavaScript que debe interpretar una expresión en lugar de un bloque. + +::: + +### Operadores rest y spread {#rest-and-spread-operators} + +El operador spread o rest `...` sirve para varias cosas. Puede ser bastante +difícil de entender, pero en OpenFn tiene un par de usos muy útiles. + +Primero, puedes **"esparcir"** (spread) o **"aplicar"** las propiedades y los +valores de uno o más objetos en un objeto nuevo. Es una forma muy práctica de +hacer una copia superficial de un objeto. + +Funciona de forma muy parecida a `Object.assign(obj, first, second, third)`. + +Así se hace una copia superficial con spread: + +```js +const newState = { + ...state, +}; +``` + +Las propiedades se declaran en orden, así que puedes esparcir un objeto y luego +declarar más propiedades: + +```js +const newState = { + ...state + data: {} // create a new data object but keep all other keys of state +} +``` + +Puedes esparcir varios objetos, que también se aplican en orden. Este ejemplo +aplica algunos valores predeterminados, luego los sobrescribe con lo que haya en +el state y, por último, sobrescribe la clave data. + +```js +const newState = { + ...defaults, + ...state + data: {} // create a new data object but keep all other keys of state +} +``` + +Esparcir de esta forma no afecta al objeto original (es decir, en el ejemplo +anterior, `defaults` y `state` no cambian). Pero recuerda que solo es una copia +superficial, y que los valores no primitivos usan punteros, no copias. + +
+¿Qué es una copia superficial? + +Hacer una copia superficial de un objeto significa copiar todas las claves y los +valores de primer nivel de ese objeto en un objeto nuevo. + +Pero esto SOLO se aplica a las claves de primer nivel. Y si un valor contiene un +objeto, en realidad solo estás copiando un _puntero_ a ese objeto. + +```js +const a = { + x: 1, + y: { + values: [1, 2, 3] + } +}; + +// declare b as a shallow clone of a +const b = { + ... a +} + +b.x = 2; // a.x is unchanged +b.y.values = []; // a.y.values is changed +b.y = 20' // a.y is unchanged +``` + +Una copia profunda significa que se copian todas las propiedades de todo el +árbol del objeto. + +
+ +### Implementar reglas de mapeo y variables globales {#implementing-mapping-rules-and-global-variables} + +Si tienes una lista global de variables o reglas de mapeo que quieres usar en +tus workflows, puedes agregarlas a tu job como una constante y hacer referencia +a ella tantas veces como quieras en la expresión del job. Consulta la +documentación sobre [especificaciones de mapeo](/design/mapping-specs.md) para +obtener más información sobre las variables globales. + +```js +//Workflow step 1 +//Global mapping rules you want to implement in your workflow +const locationMap = { + //location_id from source app: location value in destination app + 01: 'Western Cape', + 02: 'Eastern Cape', + 03: 'Gauteng' +} +//First we use fn() to transform, map & clean our data +fn(state => { + // Here we build the payload of our http request body... + // We assume the input is an array of records + const payload = state.data.map(record => ({ + location: locationMap[record.location_id] //translate location_id to the mapped value + external_id: record.case_id + })); + + return {...state, payload}; +}); + +//Workflow step 2 +//Then we post the payload built in the prior operation to create a record +post('/api/myEndpoint', { + headers: { + 'Content-Type': 'application/json', + }, + body: (state) => state.payload +}); +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-examples.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-examples.md new file mode 100644 index 000000000000..9a0755c17d63 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-examples.md @@ -0,0 +1,424 @@ +--- +title: Ejemplos de código de jobs +sidebar_label: Ejemplos de jobs +translation_source_hash: e6b804b61b644b95048adfce67db6c76a4519068 +translation_review_status: machine +--- + +Aquí encontrarás bloques de código para distintas funciones y formas de manejar +datos que puedes usar en tus jobs. + +:::tip + +Para ver jobs de ejemplo escritos por el equipo principal de OpenFn y otros +usuarios, consulta la [biblioteca](/adaptors/library) u otros repositorios de +proyectos en [GitHub.com/OpenFn](https://github.com/OpenFn). + +::: + +:::info ¿Preguntas? + +Si tienes preguntas sobre cómo escribir jobs, pregunta en la +[comunidad](https://community.openfn.org) para recibir ayuda del equipo +principal de OpenFn y de otros implementadores. + +::: + +### Expresión de job (de CommCare a SF) {#job-expression-for-commcare-to-sf} + +La siguiente expresión de job toma un recibo que coincide y usa sus datos para +hacer un upsert de un registro `Patient__c` en Salesforce y crear varios +registros nuevos de `Patient_Visit__c` (hijos de Patient). + +```js +upsert( + 'Patient__c', + 'Patient_Id__c', + fields( + field('Patient_Id__c', dataValue('form.patient_ID')), + relationship('Nurse__r', 'Nurse_ID_code__c', dataValue('form.staff_id')), + field('Phone_Number__c', dataValue('form.mobile_phone')) + ) +); +each( + join('$.data.form.visits[*]', '$.references[0].id', 'Id'), + create( + 'Visit__c', + fields( + field('Patient__c', dataValue('Id')), + field('Date__c', dataValue('date')), + field('Reason__c', dataValue('why_did_they_see_doctor')) + ) + ) +); +``` + +### Acceder al "data array" en los envíos de Open Data Kit {#accessing-the-data-array-in-open-data-kit-submissions} + +Fíjate en cómo usamos "each" para obtener los datos de cada elemento del "data +array" en ODK. + +```js +each( + '$.data.data[*]', + create( + 'ODK_Submission__c', + fields( + field('Site_School_ID_Number__c', dataValue('school')), + field('Date_Completed__c', dataValue('date')), + field('comments__c', dataValue('comments')), + field('ODK_Key__c', dataValue('*meta-instance-id*')) + ) + ) +); +``` + +### De ODK a Salesforce: crear un registro padre con muchos hijos a partir de los datos del padre {#odk-to-salesforce-create-parent-record-with-many-children-from-parent-data} + +Aquí, el usuario lleva `time_end` y `parentId` del objeto padre a cada línea de +detalle. + +```js +each( + dataPath('data[*]'), + combine( + create( + 'transaction__c', + fields( + field('Transaction_Date__c', dataValue('today')), + relationship( + 'Person_Responsible__r', + 'Staff_ID_Code__c', + dataValue('person_code') + ), + field('metainstanceid__c', dataValue('*meta-instance-id*')) + ) + ), + each( + merge( + dataPath('line_items[*]'), + fields( + field('end', dataValue('time_end')), + field('parentId', lastReferenceValue('id')) + ) + ), + create( + 'line_item__c', + fields( + field('transaction__c', dataValue('parentId')), + field('Barcode__c', dataValue('product_barcode')), + field('ODK_Form_Completed__c', dataValue('end')) + ) + ) + ) + ) +); +``` + +> **Nota: había un error conocido en la función `combine` que ya se corrigió. +> `combine` sirve para combinar dos operaciones en una y se suele usar para +> ejecutar varios `create` dentro de un `each(path, operation)`. Puedes ver el +> código fuente de combine aquí: +> [language-common: combine](https://github.com/OpenFn/language-common/blob/master/src/index.js#L204-L222)** + +### Crear muchos registros hijos SIN un grupo de repetición en ODK {#create-many-child-records-without-a-repeat-group-in-odk} + +```js +beta.each( + '$.data.data[*]', + upsert( + 'Outlet__c', + 'Outlet_Code__c', + fields( + field('Outlet_Code__c', dataValue('outlet_code')), + field('Location__Latitude__s', dataValue('gps:Latitude')), + field('Location__Longitude__s', dataValue('gps:Longitude')) + ) + ) +); +beta.each( + '$.data.data[*]', + upsert( + 'Outlet_Call__c', + 'Invoice_Number__c', + fields( + field('Invoice_Number__c', dataValue('invoice_number')), + relationship('Outlet__r', 'Outlet_Code__c', dataValue('outlet_code')), + relationship('RecordType', 'name', 'No Call Card'), + field('Trip__c', 'a0FN0000008jPue'), + relationship( + 'Sales_Person__r', + 'Sales_Rep_Code__c', + dataValue('sales_rep_code') + ), + field('Date__c', dataValue('date')), + field('Comments__c', dataValue('comments')) + ) + ) +); +``` + +### Salesforce: actualizar un registro {#salesforce-perform-an-update} + +```js +update("Patient__c", fields( + field("Id", dataValue("pathToSalesforceId")), + field("Name__c", dataValue("patient.first_name")), + field(...) +)); +``` + +### Salesforce: definir el tipo de registro con 'relationship(...)' {#salesforce-set-record-type-using-relationship} + +```js +create( + 'custom_obj__c', + fields( + relationship( + 'RecordType', + 'name', + dataValue('submission_type'), + field('name', dataValue('Name')) + ) + ) +); +``` + +### Salesforce: definir el tipo de registro con el ID del tipo de registro {#salesforce-set-record-type-using-record-type-id} + +```js +each( + '$.data.data[*]', + create( + 'fancy_object__c', + fields( + field('RecordTypeId', '012110000008s19'), + field('site_size', dataValue('size')) + ) + ) +); +``` + +### Telerivet: enviar un SMS a partir de una alerta de workflow de Salesforce {#telerivet-send-sms-based-on-salesforce-workflow-alert} + +```js +send( + fields( + field( + 'to_number', + dataValue( + 'Envelope.Body.notifications.Notification.sObject.phone_number__c' + ) + ), + field('message_type', 'sms'), + field('route_id', ''), + field('content', function (state) { + return 'Hey there. Your name is '.concat( + dataValue('Envelope.Body.notifications.Notification.sObject.name__c')( + state + ), + '.' + ); + }) + ) +); +``` + +### HTTP: hacer fetch sin fallar {#http-fetch-but-dont-fail} + +```js +// ============= +// We use "fetchWithErrors(...)" so that when the +// SMS gateway returns an error the run does not "fail". +// It "succeeds" and then delivers that error message +// back to Salesforce with the "Update SMS Status" job. +// ============= +fetchWithErrors({ + getEndpoint: 'send_to_contact', + query: function (state) { + return { + msisdn: + state.data.Envelope.Body.notifications.Notification.sObject + .SMS__Phone_Number__c, + message: + state.data.Envelope.Body.notifications.Notification.sObject + .SMS__Message__c, + api_key: 'some-secret-key', + }; + }, + externalId: state.data.Envelope.Body.notifications.Notification.sObject.Id, + postUrl: 'https://www.openfn.org/inbox/another-secret-key', +}); +``` + +### Ejemplo de job para la API de eventos de DHIS2 {#sample-dhis2-events-api-job} + +```js +event( + fields( + field('program', 'eBAyeGv0exc'), + field('orgUnit', 'DiszpKrYNg8'), + field('eventDate', dataValue('properties.date')), + field('status', 'COMPLETED'), + field('storedBy', 'admin'), + field('coordinate', { + latitude: '59.8', + longitude: '10.9', + }), + field('dataValues', function (state) { + return [ + { + dataElement: 'qrur9Dvnyt5', + value: dataValue('properties.prop_a')(state), + }, + { + dataElement: 'oZg33kd9taw', + value: dataValue('properties.prop_b')(state), + }, + { + dataElement: 'msodh3rEMJa', + value: dataValue('properties.prop_c')(state), + }, + ]; + }) + ) +); +``` + +### Ejemplo de job para la API de data value sets de DHIS2 {#sample-dhis2-data-value-sets-api-job} + +```js +dataValueSet( + fields( + field('dataSet', 'pBOMPrpg1QX'), + field('orgUnit', 'DiszpKrYNg8'), + field('period', '201401'), + field('completeData', dataValue('date')), + field('dataValues', function (state) { + return [ + { dataElement: 'f7n9E0hX8qk', value: dataValue('prop_a')(state) }, + { dataElement: 'Ix2HsbDMLea', value: dataValue('prop_b')(state) }, + { dataElement: 'eY5ehpbEsB7', value: dataValue('prop_c')(state) }, + ]; + }) + ) +); +``` + +### Ejemplo de expresión de OpenMRS que crea una persona y luego un paciente {#sample-openmrs-expression-creates-a-person-and-then-a-patient} + +```js +person( + fields( + field('gender', 'F'), + field('names', function (state) { + return [ + { + givenName: dataValue('form.first_name')(state), + familyName: dataValue('form.last_name')(state), + }, + ]; + }) + ) +); +patient( + fields( + field('person', lastReferenceValue('uuid')), + field('identifiers', function (state) { + return [ + { + identifier: '1234', + identifierType: '8d79403a-c2cc-11de-8d13-0010c6dffd0f', + location: '8d6c993e-c2cc-11de-8d13-0010c6dffd0f', + preferred: true, + }, + ]; + }) + ) +); +``` + +### Combinar muchos valores en una ruta hija {#merge-many-values-into-a-child-path} + +```js +each( + merge( + dataPath("CHILD_ARRAY[*]"), + fields( + field("metaId", dataValue("*meta-instance-id*")), + field("parentId", lastReferenceValue("id")) + ) + ), + create(...) +) +``` + +### arrayToString {#arraytostring} + +```js +arrayToString(arr, separator_string); +``` + +### Acceder a la URL de una imagen en un envío de ODK {#access-an-image-url-from-an-odk-submission} + +```js +// In ODK the image URL is inside an image object... +field("Photo_URL_text__c", dataValue("image.url")), +``` + +### alterState (alterar el state) para asegurarte de que los datos estén en un array {#alterstate-alter-state-to-make-sure-data-is-in-an-array} + +```js +// Here, we make sure CommCare gives us an array to use in each(merge(...), ...) +fn(state => { + const idCards = state.data.form.ID_cards_given_to_vendor; + if (!Array.isArray(idCards)) { + state.data.form.ID_cards_given_to_vendor = [idCards]; + } + return state; +}); + +// Now state has been changed, and we carry on... +each( + merge( + dataPath('form.ID_cards_given_to_vendor[*]'), + fields( + field('Vendor_Id', dataValue('form.ID_vendor')), + field('form_finished_time', dataValue('form.meta.timeEnd')) + ) + ), + upsert( + 'Small_Packet__c', + 'sp_id__c', + fields( + field('sp_id__c', dataValue('ID_cards_given_to_vendor')), + relationship('Vendor__r', 'Badge_Code__c', dataValue('Vendor_Id')), + field( + 'Small_Packet_Distribution_Date__c', + dataValue('form_finished_time') + ) + ) + ) +); +``` + +### Iniciar sesión en un servidor con un certificado SSL personalizado {#log-in-to-a-server-with-a-custom-ssl-certificate} + +Este fragmento muestra cómo conectarte a un servidor seguro sin verificar el +certificado SSL. Define `strictSSL: false` en el argumento de opciones de la +función `post` de `language-http`. + +```js +post( + `${state.configuration.url}/${path}`, + { + headers: { 'content-type': 'application/json' }, + body: { + email: 'Luka', + password: 'somethingSecret', + }, + strictSSL: false, + }, + callback +); +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-snippets.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-snippets.md new file mode 100644 index 000000000000..1f28fbe96ed0 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-snippets.md @@ -0,0 +1,131 @@ +--- +title: Fragmentos de código +sidebar_label: Fragmentos de código +translation_source_hash: 45bf7a84c28b09f0af8a976d6d3b790ebc094a9d +translation_review_status: machine +--- + +Esta sección reúne varios fragmentos de código JavaScript útiles que puedes usar +en tus jobs. + +La mayoría de los fragmentos están implementados como callbacks de otras +operaciones. + +Puedes copiar estos callbacks y adaptarlos a tu propio código. + +## General {#general} + +### Reemplazo personalizado {#custom-replacer} + +```js +field('destination__c', state => { + return dataValue('path_to_data')(state).toString().replace('cats', 'dogs'); +}); +``` + +Esto reemplaza todos los "cats" por "dogs" en la cadena que está en +`path_to_data`. + +> **NOTA:** La función `replace()` de JavaScript solo reemplaza la primera +> aparición del argumento que indiques. Si buscas una forma de reemplazar todas +> las apariciones, te sugerimos usar una regex como hicimos en el +> [ejemplo](#concatenation-of-null-values) de más abajo. + +### arrayToString personalizado {#custom-arraytostring} + +```js +field("target_specie_list__c", function(state) { + return Array.apply( + null, sourceValue("$.data.target_specie_list")(state) + ).join(', ') +}), +``` + +Toma un array y concatena cada elemento en una cadena, con ", " como separador. + +### Concatenación personalizada {#custom-concatenation} + +```js +field('ODK_Key__c', function (state) { + return dataValue('metaId')(state).concat('(', dataValue('index')(state), ')'); +}); +``` + +Esto concatena dos valores. + +### Concatenación de valores nulos {#concatenation-of-null-values} + +Esto concatena muchos valores, aunque uno o más sean nulos, y los escribe en un +campo llamado Main_Office_City_c. + +```js +... + field("Main_Office_City__c", function(state) { + return arrayToString([ + dataValue("Main_Office_City_a")(state) === null ? "" : dataValue("Main_Office_City_a")(state).toString().replace(/-/g, " "), + dataValue("Main_Office_City_b")(state) === null ? "" : dataValue("Main_Office_City_b")(state).toString().replace(/-/g, " "), + dataValue("Main_Office_City_c")(state) === null ? "" : dataValue("Main_Office_City_c")(state).toString().replace(/-/g, " "), + dataValue("Main_Office_City_d")(state) === null ? "" : dataValue("Main_Office_City_d")(state).toString().replace(/-/g, " "), + ].filter(Boolean), ',') + }) +``` + +> Fíjate en que esta función personalizada usa la **regex** `/-/g` para +> asegurarse de que se tengan en cuenta todas las apariciones (g = búsqueda +> global). + +### ID personalizado de la enésima referencia {#custom-nth-reference-id} + +Si alguna vez quieres obtener el PRIMER objeto que creaste, o el SEGUNDO, o el +enésimo, una función como esta te sirve. + +```js +field('parent__c', function (state) { + return state.references[state.references.length - 1].id; +}); +``` + +Fíjate en que, en lugar de tomar el id de lo "último" que se creó en Salesforce, +tomas el id de lo primero, o de lo segundo si reemplazas "length-1" por +"length-2". + +## Salesforce {#salesforce} + +### Convertir una cadena de fecha al formato ISO estándar para Salesforce {#convert-date-string-to-standard-iso-date-for-salesforce} + +```js +field('Payment_Date__c', function (state) { + return new Date(dataValue('payment_date')(state)).toISOString(); +}); +``` + +> **NOTA**: La salida de esta función siempre tiene el formato de la zona +> horaria GMT. + +### Usar campos de ID externo para las relaciones en una carga masiva en Salesforce {#use-external-id-fields-for-relationships-during-a-bulk-load-in-salesforce} + +```js +array.map(item => { + return { + Patient_Name__c: item.fullName, + 'Account.Account_External_ID__c': item.account + 'Clinic__r.Unique_Clinic_Identifier__c': item.clinicId, + 'RecordType.Name': item.type, + }; +}); +``` + +### Upsert masivo con un ID externo en Salesforce {#bulk-upsert-with-an-external-id-in-salesforce} + +```js +bulk( + 'Visit_new__c', + 'upsert', + { + extIdField: 'commcare_case_id__c', + failOnError: true, + allowNoOp: true, + }, + dataValue('patients') +); +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-writing-guide.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-writing-guide.md new file mode 100644 index 000000000000..6187c00f9d54 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/job-writing-guide.md @@ -0,0 +1,75 @@ +--- +sidebar_label: Introducción +title: Guía para escribir jobs +translation_source_hash: 9d3ec7d47868cbee41ed5f200085af202ed96bfe +translation_review_status: machine +--- + +En OpenFn, la automatización de workflows y la integración de datos se logran +mediante la creación de jobs. + +Esta guía te presenta los conceptos clave y las buenas prácticas para escribir +jobs. Sirve tanto para quienes recién empiezan a programar como para +programadores de JavaScript con experiencia. De hecho, incluso si ya tienes +experiencia con JavaScript, hay varios patrones clave del ecosistema de OpenFn +que es importante aprender. + +:::tip + +Si escribes jobs en la app de la plataforma (Lightning), puedes usar el +[AI Assistant](/build/ai-assistant.md) como ayuda. Lo encontrarás en el +Inspector. + +::: + +Un job es un conjunto de código JavaScript que realiza una tarea concreta, como +obtener datos de Salesforce o convertir datos JSON al estándar FHIR. + +Cada job usa exactamente un adaptor (a menudo llamado "conector") para realizar +su tarea. El adaptor ofrece un conjunto de funciones auxiliares (operaciones) +que facilitan la comunicación con una fuente de datos. + +Esta guía sirve por igual para escribir jobs en la app (Lightning) o con la CLI. + +:::info Workflows + +Puedes encadenar varios jobs en un workflow. Un patrón común es usar un job para +obtener datos de la fuente de datos A, otro job para convertir o transformar +esos datos para que sean compatibles con la fuente de datos B, y un tercer job +para subir los datos transformados a la fuente de datos B. + +Para saber más sobre el diseño y la implementación de workflows, consulta +[Crear y gestionar workflows](/build/workflows.md). + +::: + +## Próximos pasos {#next-steps} + +La mejor forma de aprender a escribir jobs de OpenFn es escribir jobs de OpenFn. + +Puedes [empezar con la CLI](/build-for-developers/cli-intro.md) y ejecutar jobs +en tu computadora. Después, mira el +[desafío de la CLI](/build-for-developers/cli-challenges.md) para poner a prueba +de verdad tus habilidades para escribir jobs. + +Si ya quieres empezar a usar la app, mira esta guía para +[crear tu primer workflow](/build/workflows.md). + +El diseño de workflows no es un problema trivial, así que quizás también quieras +revisar la [documentación del proceso de diseño](/design/design-overview.md) de +workflows. + +A medida que tus jobs crecen, empezarás a escribir funciones auxiliares dentro +de ellos para analizar, mapear o reformatear datos. Puedes hacer pruebas +unitarias de esas funciones como con cualquier otro código JavaScript: +expórtalas en el nivel superior, compila tu proyecto con la CLI y apunta un +ejecutor de pruebas a la salida. Consulta +[Escribir pruebas unitarias para tus jobs](/documentation/jobs/unit-testing-jobs). + +:::info ¿Preguntas? + +Si tienes preguntas sobre cómo escribir jobs, pregunta en la +[comunidad](https://community.openfn.org) para recibir ayuda del equipo +principal de OpenFn y de otros implementadores. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/lazy-state-operator.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/lazy-state-operator.md new file mode 100644 index 000000000000..5e50e5ae93cb --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/lazy-state-operator.md @@ -0,0 +1,181 @@ +--- +sidebar_label: Operador lazy state +title: El operador lazy state +translation_source_hash: 13268eb8864e380f9070d38359a4cb3ab44acf65 +translation_review_status: machine +--- + +## El operador lazy state {#the-lazy-state-operator} + +:::tip Funcionalidad experimental + +El operador lazy state llegó a OpenFn en abril de 2024. Todavía se considera una +funcionalidad experimental, pero funciona muy bien, ¡y te animamos a usarlo! + +Si tienes comentarios, problemas o sugerencias sobre el operador lazy state, +¡nos encantaría saber de ti en la [comunidad](https://community.openfn.org)! +También puedes abrir un issue en [GitHub](https://github.com/openfn/kit/issues). + +::: + +El operador lazy state es una sintaxis abreviada que facilita leer el state +cuando pasas datos a una operación. + +En lugar de escribir `state.data` para acceder a algo del state, puedes usar +`$`, así: + +```js +get($.data.url); +``` + +El `$` garantiza que el valor que se pasa a la operación se resuelva en el +momento correcto. Piensa en ello como pasar una ruta a una parte del state, en +lugar de pasar el valor de esa ruta. + +Lo bueno es que básicamente puedes ignorar por completo la sección anterior y no +pensar demasiado en cuándo se evalúa el state. Solo lee de `$` como si fuera tu +objeto state, y el runtime de OpenFn resolverá el valor correctamente en el +momento de la ejecución. + +El símbolo `$` es en realidad solo azúcar sintáctico para `(state) => state` (en +la mayoría de los casos, solo hacemos un reemplazo de texto al compilar tu +código). Estas dos sentencias se comportan exactamente igual: + +```js +get($.data.url); +get((state) => state.data.url); +``` + +Lo llamamos "lazy state" (state diferido) porque el motor del runtime resuelve +la referencia justo antes de usarla. Así se evitan muchos de los problemas de +asincronía de JavaScript que se explican en +[Leer el state de forma diferida](/jobs/operations.md#reading-state-lazily). + +:::tip $ solo funciona dentro de las operaciones + +`$` solo funciona dentro de una expresión que se pasa a una operación. Dicho de +otro modo, solo puedes usarlo donde podrías escribir `(state) => state` en su +lugar (como en el ejemplo anterior). + +::: + +## Ejemplos de uso {#usage-examples} + +Los siguientes fragmentos de código muestran algunas formas de usar el operador +lazy state. Cada ejemplo se puede reescribir sin `$`, pero con él la sintaxis es +más corta, más legible y más expresiva. + +El uso básico es simplemente pasar el state a una operación: + +```js +upsert('patient', $.data.patients[0]); +``` + +Puedes usarlo dentro de un objeto (siempre que ese objeto se pase a una +operación): + +```js +create('agent', { + name: $.patient.name, + country: $.patient.country, +}); +``` + +Puedes usarlo dentro de una plantilla de string: + +```js +get(`/patients/${$.patient.id}`); +``` + +O dentro de otras expresiones, como una concatenación: + +```js +create({ + name: $.patients[0].first_name + ' ' + $.patients[0].last_name, +}); +``` + +O en operaciones matemáticas: + +```js +create({ + profit: $.report.revenue - $.report.expenses, +}); +``` + +Puedes usarlo al mapear estructuras de datos: + +```js +create('user', { + countryCode: countries[$.location.country], +}); +``` + +Y puedes usarlo en operaciones anidadas, como con `each()`: + +```js +each($.data.patients, + post(`patients/${$.data.patient.id}`, $.data.patient) +); +``` + +## $ no es state {#-is-not-state} + +El operador `$` **no** es un alias de `state`. + +No se puede usar en lugar de la variable `state`. No se le puede asignar un +valor ni puede estar en el lado izquierdo de una asignación, y solo se puede +usar dentro de un argumento de una función. + +Esto también significa que el operador lazy state solo sirve para LEER el state. +No se puede usar para asignar valores directamente al state. + +Todos estos ejemplos son errores: + +```js +❌ const url = $.data.url; +get(url); + +❌ get(() => $.data.url); + +❌ $.data.x = fn(); + +❌ fn(state => { + $.data.x = 10; +}); +``` + +
+Reglas de compilación para usuarios avanzados + +¿Cómo funciona el operador lazy state? La "magia" está en el compilador. + +En pocas palabras, cada vez que el compilador ve `$` en tu código, lo reemplaza +por `(state) => state`. Así: + +``` +get($.data.url) // compiles to get((state) => state.data.url) +``` + +En la práctica, las reglas son un poco más complicadas. Cuando ve un operador +`$`, el compilador primero comprueba que `$` no se haya declarado como variable +o parámetro. Si se declaró, lo ignora por completo. + +Pero si considera que `$` es un operador de state, el compilador primero +reemplaza el símbolo `$` por `state`, luego busca la operación a la que se está +llamando y, por último, envuelve el argumento en una función flecha (si todavía +no lo está). + +``` +get({ url: $.data.url }) // compiles to get((state) => { url: state.data.url }) +``` + +Este "hoisting" de la función flecha permite usar expresiones más complejas e +interesantes con lazy state, como plantillas de string o búsquedas dinámicas en +objetos. + +Si tienes curiosidad (o necesitas resolver algún problema), puedes usar el +comando `openfn compile` de la CLI para ver el código compilado, que te mostrará +cómo trata el compilador tus operadores de state. + +
diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/operations.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/operations.md new file mode 100644 index 000000000000..d19134ebcca9 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/operations.md @@ -0,0 +1,405 @@ +--- +sidebar_label: Operaciones +title: Operaciones +translation_source_hash: 33c7730014de711a9d329f1afb74aa6c7312b32b +translation_review_status: machine +--- + +Las operaciones son funciones de JavaScript que expone un adaptor y que se usan +en el código del job para _hacer cosas_. + +Las operaciones las proporciona un adaptor (conector). Cada adaptor exporta una +lista de funciones pensadas para interactuar con una fuente de datos en +particular. Por ejemplo, mira los adaptors de +[dhis2](/adaptors/packages/dhis2-docs) y +[salesforce](/adaptors/packages/salesforce-docs). + +Todo lo que puedes lograr en OpenFn también se puede lograr con bibliotecas de +JavaScript existentes o con llamadas a APIs REST. El valor de los adaptors está +en que ofrecen funciones que lo hacen más fácil: se encargan de la autorización, +ofrecen una sintaxis más limpia y te ocultan los detalles de implementación. + +Por ejemplo, así de simple es hacer una solicitud GET con el adaptor http: + +```js +get('/patients'); +``` + +El primer argumento de `get` es la ruta a la que se hace la solicitud (la +configuración le indica al adaptor qué URL base usar). En este caso pasamos una +cadena fija, pero también podemos pasar un valor del state: + +```js +get(state => state.endpoint); +``` + +
+¿Por qué la función flecha? + +Si tienes algo de experiencia con JavaScript, notarás que el ejemplo anterior +usa una función flecha para obtener la clave endpoint del state. + +Pero ¿por qué no hacer simplemente esto? + +``` +get(state.endpoint); +``` + +El problema es que el valor del state debe resolverse de forma diferida (es +decir, justo antes de que el get se ejecute). Por cómo funciona JavaScript, si +escribimos el valor directamente, podría leerse antes de que se haya asignado +state.endpoint. + +Para más detalles, salta a +[Leer el state de forma diferida](#reading-state-lazily). + +
+ +El código de tu job solo debería contener operaciones en el nivel superior +(alcance superior). NO deberías incluir ninguna otra instrucción de JavaScript. +Hablaremos más de esto en un momento. + +## Las operaciones se ejecutan en el nivel superior {#operations-run-at-the-top-level} + +Las operaciones solo funcionan cuando están en el nivel superior del código de +tu job: + +```js +get('/patients'); +each('$.data.patients[*]', state => { + item.id = `item-${index}`; + return state; +}); +post('/patients', dataValue('patients')); +``` + +OpenFn llama a tus operaciones en serie durante la ejecución del workflow y se +asegura de que cada una reciba el state correcto. + +Si intentas anidar una operación dentro del callback de otra operación, fallará: + +```js +get('/patients', { headers: { 'content-type': 'application/json' } }, state => { + // This will fail because it is nested in a callback + each('$.data.patients[*]', (item, index) => { + item.id = `item-${index}`; + }); +}); +post('/patients', dataValue('patients')); +``` + +Esto se debe a que una operación es una función "fábrica": cuando se ejecuta, +devuelve una nueva función que debe invocarse con el state. El runtime de OpenFn +solo maneja esto correctamente en el alcance superior. La buena práctica es +construir cada operación del pipeline en el nivel superior y dejar que el state +pase de una a otra de forma natural. + +Si alguna vez de verdad necesitas una operación anidada, puedes invocarla de +inmediato y pasarle el state directamente, pero esto es un antipatrón y deberías +evitarlo: + +```js +get('/patients', { headers: { 'content-type': 'application/json' } }, state => { + each('$.data.patients[*]', (item, index) => { + item.id = `item-${index}`; + })(state); // anti-pattern: immediately invoke and pass state +}); +post('/patients', dataValue('patients')); +``` + +## Leer el state de forma diferida {#reading-state-lazily} + +Un problema común al escribir jobs es obtener el valor correcto del state en el +momento correcto. Mira este código: + +```js +get('/some-data'); +post('/some-other-data', state.data); +``` + +El `state.data` de la llamada a `post` se resolverá como `undefined` y el post +fallará. Esto se debe a que las operaciones son funciones fábrica: sus +parámetros se resuelven cuando se carga el módulo (antes de que se haya +ejecutado cualquier operación), así que `state.data` todavía no tiene un valor +asignado cuando `post` lo lee. + +La solución es pasar una función en lugar de un valor, para que la evaluación se +posponga hasta que la operación se ejecute de verdad: + +```js +get('/some-data'); +post('/some-other-data', state => state.data); +``` + +Cuando `post` se ejecuta, resuelve cualquier argumento que sea una función +llamándola con el state actual. Este patrón de evaluación diferida es +fundamental para escribir jobs de OpenFn correctos. Consulta también el +[operador lazy state](/jobs/lazy-state-operator.md) para una sintaxis abreviada. + +## Callbacks y fn() {#callbacks-and-fn} + +:::caution + +A partir de julio de 2024, los callbacks se irán eliminando de las APIs de los +adaptors. Consulta [Operaciones y promesas](#operations-and-promises) para ver +consejos sobre cómo usar callbacks con APIs de adaptors que no los admiten de +forma explícita. + +::: + +Muchas operaciones te dan acceso a una función callback. + +Los callbacks se invocan con el state, ejecutan el código que quieras y deben +devolver el siguiente state. Por lo general, tu callback se invoca como el +último paso de una operación. + +Esto es útil para interceptar y manipular el valor que devuelve una operación. + +
+¿Qué es un callback? + +Un callback es un patrón común en JavaScript. + +Es algo difícil de entender en abstracto: un callback es una función que le +pasas a otra función para que esta la invoque en un momento determinado. + +Se explica mejor con un ejemplo. Todos los arrays de JavaScript tienen una +función llamada `map`, que recibe un único argumento: un callback. + +Array.map recorre cada elemento del array, invoca tu función callback con él, +guarda el resultado en un array nuevo y, cuando termina, devuelve ese array. + +```js +const array = ['a', 'b', 'c']; +const result = array.map(item => { + return item.toUpperCase(); +}); +console.log(array); // ['a', 'b', 'c']; +console.log(result); // ['A', 'B', 'C']; +``` + +Como en JavaScript las funciones son datos, podemos reescribir ese código así +(quizás quede un poco más legible): + +```js +const array = ['a', 'b', 'c']; +const upperCase = item => { + return item.toUpperCase(); +}; +const result = array.map(upperCase); +console.log(array); // ['a', 'b', 'c']; +console.log(result); // ['A', 'B', 'C']; +``` + +
+ +La función `fn()`, por ejemplo, SOLO te permite definir un callback. Esto es +útil para ejecutar código arbitrario: si quieres pasar a JavaScript puro, así es +como se hace: + +```js +fn(state => { + // declare a helper function + const convertToFhir = item => { + /* ... */ + }; + + // Map data into a new format with native Javascript functions + state.transformed = state.data.map(convertToFhir); + + // Always return the state + return state; +}); +``` + +Muchas otras operaciones aceptan un argumento callback. En ese caso, tu callback +se invoca con el state y debe devolver el state final como resultado de la +operación. + +Por ejemplo, imagina que obtienes datos de una fuente de datos y recibes un +bloque de JSON. Quizás quieras filtrar esos datos antes de pasarlos a la +siguiente operación. + +Podrías intentar, de forma ingenua, algo como esto, ¡pero no va a funcionar! + +```js +get('/data'); // writes to state.data +state.data = state.data.filter(/* ... */); // This is invalid! +``` + +Podrías usar otra operación, como `fn` o `each`, y a menudo funcionan muy bien: + +```js +get('/data'); +fn(state => { + state.data = state.data.filter(/* ... */); + return state; +}); +``` + +Pero también puedes usar una función callback, que suele quedar un poco más +ordenada: + +```js +get('/data', {}, state => { + state.data = state.data.filter(/* ... */); + return state; +}); +``` + +Lo que devuelva tu callback se usará como state de entrada de la siguiente +operación (o será el state final del job). Así que recuerda devolver SIEMPRE el +state. + +Ten en cuenta que algunos adaptors escriben información interna en el state. Por +eso, por lo general deberías usar `return { ... state }` en lugar de +`return { data: state.data }`. + +:::tip + +¡Recuerda! Devuelve siempre el state desde un callback. + +::: + +## Operaciones y promesas {#operations-and-promises} + +:::tip + +El soporte para promesas se agregó en julio de 2024 en `@openfn/compiler@0.2.0`. +Está disponible en la CLI a partir de la versión 1.7.0 y en el worker de +Lightning a partir de la versión 1.4.0. + +::: + +Las operaciones se comportan como las promesas de JavaScript, ya que tienen las +funciones `.then()` y `.catch()`. Esto es útil para crear tus propios callbacks +y manejar errores. + +:::info Nota para desarrolladores + +El compilador es el que agrega el soporte para .then(). Técnicamente, las +operaciones no devuelven una promesa, sino una función, pero el compilador +modifica el código del job y envuelve la operación en una llamada a una promesa +diferida. + +::: + +### Callback con then() {#callback-with-then} + +Puedes encadenar `then()` en cualquier operación. Recibe un callback que se +ejecuta cuando la operación termina. + +El callback recibe el state que devuelve la operación y debe devolver el objeto +state que se pasará a la _siguiente_ operación. + +Por ejemplo: + +```js +get($.data.url).then(state => { + console.log(state.data); + return state; // always remember to return state! +}); +``` + +Si conoces el patrón de callbacks de nuestros adaptors, `.then()` cumple +exactamente la misma función que un callback. Te da la oportunidad de +transformar el state que devuelve una operación. + +Por lo general no necesitas un callback ni un `.then()`: puedes ejecutar las +operaciones en serie. El siguiente código es funcionalmente igual al ejemplo +anterior: + +```js +get($.data.url); +fn(state => { + console.log(state.data); + return state; // always remember to return state! +}); +``` + +`.then()` resulta especialmente útil al combinar operaciones con _state con +alcance_, como con `each()`: + +```js +each($.items, post(`patient/${$.data.id}`, $.data)); +``` + +:::tip + +Puedes leer más sobre la operación `each()` en +[Iteración con each](/jobs/data-transformation.md#iteration-with-each). + +::: + +La función `each` recibe un array y, por cada elemento, invoca un callback con +un state con alcance. Es decir, toma tu objeto state y asigna el elemento de la +iteración a `state.data`. Dicho de otro modo, dentro del callback, `state.data` +tiene como _alcance_ cada elemento del array. + +```js +each($.items, state => { + console.log(state.data); // each item in the items array + console.log(state.index); // the current index of iteration + return state; +}); +``` + +Así, en el ejemplo anterior, cada elemento de `state.items` se pasará a una +función HTTP `post()`, que incluirá el id en una URL y subirá el elemento al +servidor. + +¿Y si quieres hacer algo con el state con alcance DESPUÉS de la solicitud? +Quizás quieras revisar el código de estado y registrar un error, o modificar los +datos antes de volver a escribirlos en el state. + +Para esto puedes encadenar `operation().then()`: + +```js +each( + $.items, + post(`patient/${$.data.id}`, $.data).then(state => { + state.completed.push(state.data); + return state; + }) +); +``` + +Ahora esta expresión: + +- Recorre cada elemento de `state.items` +- Llama a la operación post con el state con alcance (es decir, el elemento en + `state.data`) +- Cuando el post termina, pasa el resultado como state con alcance al callback + de `.then()` + +### Manejo de errores con catch() {#error-handling-with-catch} + +La mayoría de los adaptors lanzan un error cuando algo sale mal, lo que puede +hacer que el job (y quizás incluso el workflow) termine antes de tiempo. + +Como cada operación tiene un `catch()`, puedes interceptar el error en el código +de tu job e incluso suprimirlo. + +```js +get('patients').catch((error, state) => { + state.error = error; + return state; +}); +``` + +El callback de error recibe dos argumentos: el error que lanzó el adaptor y el +objeto state. + +Si quieres que la ejecución continúe, deberías devolver el objeto state desde el +catch. Ese state se pasará luego a la siguiente operación. + +Si _sí_ quieres detener la ejecución, quizás con algún registro para depurar o +con un error diferente, deberías lanzar el error desde dentro del manejador +catch. + +```js +get('patients').catch((error, state) => { + console.log('Error ocurred faithing patients', error); + throw error; +}); +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/state.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/state.md new file mode 100644 index 000000000000..be67798cbc3d --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/state.md @@ -0,0 +1,229 @@ +--- +title: State de entrada y de salida +translation_source_hash: 5ac34f5e0595bb2aec97356e6635bfb55b9cff4c +translation_review_status: machine +--- + +Cada job necesita un state de entrada y, en la mayoría de los casos, produce un +state de salida. En este artículo se explican estos conceptos con más detalle. + +Todo job es un pipeline de transformación de datos. Recibe una entrada (un +objeto de JavaScript al que llamamos state) y ejecuta una serie de operaciones +(o funciones) que transforman ese state una tras otra. El objeto state final se +devuelve como la salida del pipeline. + +![Pipeline de un job](/img/guide-job-pipeline.webp) + +El state final de un job siempre debe ser un objeto de JavaScript serializable +(es decir, un objeto JSON). Se eliminan todas las claves que no se puedan +serializar. + +![Resumen del state de un job](/img/state-javascript.webp) + +:::tip Una nota sobre la terminología + +Al state de entrada también se le suele llamar _state inicial_, y al state de +salida, _state final_. Puedes usar estos términos indistintamente. + +::: + +## Claves del state {#state-keys} + +Los objetos state suelen tener las siguientes claves: + +- `data`: un almacén temporal de información, que se suele usar para guardar el + resultado de una operación concreta +- `configuration`: un objeto que contiene los datos de la credencial +- `references`: un historial de los valores anteriores de `data` +- `response`: los adaptors (como http) lo usan a menudo para guardar la + respuesta http sin procesar de una solicitud +- `errors`: una lista de los errores generados por un workflow concreto, + indexados por el nombre del job. + +Al final de un job, se elimina la clave configuration, junto con cualquier otra +clave que no se pueda serializar. + +A veces los adaptors escriben información adicional en el state durante un run. +Por ejemplo, los adaptors de bases de datos suelen escribir una clave `client` +en el state para hacer seguimiento de la conexión a la base de datos. Estas +claves se eliminan al final de un job. + +## State de entrada y de salida de los runs {#input--output-state-for-runs} + +El state de entrada de un run se genera de forma distinta según si ejecutas los +workflows localmente con la CLI o en la app: + +- Cuando creas una work order manualmente, tienes que seleccionar o generar la + entrada a mano (por ejemplo, creando un `Input` personalizado en la app, o un + archivo `state.json` si trabajas localmente + [en la CLI](/build-for-developers/cli-intro.md)). +- Cuando una work order se crea automáticamente mediante un trigger webhook o un + trigger cron, el state se crea como se describe más abajo. + +El state final de un run depende de lo que devuelva la última operación. +Recuerda que las expresiones de un job son una serie de operaciones: cada una +recibe el state y devuelve el state, después de generar cualquier número de +efectos secundarios. El state que se devuelve al final determina la salida del +run cuando terminan todas estas operaciones. + +Una buena práctica es incluir un último paso de limpieza del state que elimine +los datos que no deberían conservarse entre runs ni formar parte de la salida +(como información de identificación personal o PII), por ejemplo: + +```js +// get data from a data source +get('https://jsonplaceholder.typicode.com/users'); + +// store retrieved data in state for use later in job +fn(state => { + state.users = state.data; + return state; +}); + +// get more data from another data source +get('https://jsonplaceholder.typicode.com/posts'); + +// store additional retrieved data in state for use later in job +fn(state => { + state.posts = state.data; + return state; +}); + +// compare data +fn(state => { + if (state.users.length > state.posts.length) { + // do something based on the comparison + } + return state; +}); + +// cleanup state at the end before finishing job +fn(state => { + state.data = null; + state.users = null; + state.posts = null; + + return state; +}); +``` + +Hay algunos patrones comunes para limpiar el state final. Puedes devolver solo +las claves que necesitas: + +```js +fn(state => { + return { + data: state.data, + }; +}); +``` + +Usa el operador de propagación (spread) para conservarlo todo excepto algunas +claves que quieres sobrescribir: + +```js +fn(state => { + return { + ...state, + secretStuff: null, + }; +}); +``` + +O usa el operador _rest_ para excluir por completo algunas claves: + +```js +fn(state => { + const { username, password, secrets, ...rest } = state; + return rest; +}); +``` + +### Runs iniciados por un webhook {#webhook-triggered-runs} + +En la plataforma, cuando un evento de webhook inicia un run, el state de entrada +contiene las partes importantes de la **solicitud http** entrante. + +El state de entrada se verá más o menos así: + +```js +{ + data: { // the body of the http request + formId: "patient_enrollment", + name: "John Doe" + }, + request: { + method: "POST", + path: ['i', 'your-webhook-url-uuid'] // an ordered array with optional additional paths + headers: { "content-type": "application/json" }, // an object containing the headers of the request + query_params: {} // an object containing any query parameters + }, +} +``` + +### Runs iniciados por un cron {#cron-triggered-runs} + +Cuando un cron inicia un run, su state de entrada es el state final del run +anterior. Así, cada run sabe lo que pasó en los runs anteriores. Dicho de otro +modo, puedes pasar información de un run a otro aunque ocurran con días de +diferencia. + +**Escenario de ejemplo**: tienes una **sincronización diaria a las 9 AM** con un +workflow de 3 steps: (1) obtener los registros de pacientes, (2) transformar los +datos y (3) enviarlos a la base de datos. El lunes, el workflow procesa los +registros hasta el ID 1000 y devuelve `{ lastProcessedId: 1000 }` como state +final. El martes a las 9 AM, el cron vuelve a empezar con +`{ lastProcessedId: 1000 }` como entrada, así que sabe que tiene que obtener y +procesar los registros a partir del ID 1001. + +La primera vez que se ejecuta el workflow, el state inicial es simplemente un +objeto de JavaScript vacío: `{}` + +#### Sobrescribir la entrada del cron {#overriding-cron-input} + +Siempre puedes ejecutar manualmente un workflow con trigger cron con: + +- **Entrada vacía** (`{}`): empieza de cero, sin el state anterior. +- **Entrada personalizada**: tus propios datos, para probar escenarios + concretos. +- **Entrada predeterminada**: usa la misma entrada que los runs programados. + +Si el run manual tiene éxito, el siguiente run programado del cron empezará con +el state de salida que haya producido tu run manual. + +## State de entrada y de salida de los steps {#input--output-state-for-steps} + +El state también pasa de un step a otro dentro de un workflow. El state de +salida del step anterior se usa como state de entrada del step siguiente. + +### Si tiene éxito {#on-success} + +Cuando un job tiene éxito, su state de salida es lo que devuelva la última +operación. + +```js +{ + data: { patients: [] }, + references: [1, 2, 3] +} +``` + +### Si falla {#on-failure} + +Cuando falla un step de un workflow, el error se agrega a un objeto `errors` en +el state, con el ID del job que falló como clave. + +```js +{ + data: { patients: [] }, + references: [1, 2, 3], + errors: { + jobId: { /* error details */ } + } +} +``` + +En el siguiente diagrama puedes ver cómo podría pasar el state entre los steps +de un workflow. + +![Paso del state entre steps](/img/passing-state-steps.webp) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/unit-testing-jobs.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/unit-testing-jobs.md new file mode 100644 index 000000000000..2344c0399f23 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/unit-testing-jobs.md @@ -0,0 +1,226 @@ +--- +sidebar_label: Pruebas unitarias de jobs +title: Escribir pruebas unitarias para tus jobs +translation_source_hash: 10c76a4fa251a5d9afb0b0df0d7b2dfe65f8ef6a +translation_review_status: machine +--- + +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; + +La mayor parte del código de un job sigue el mismo patrón: obtiene algunos +registros, les cambia la forma y los envía a otro lugar. Pero esa parte de +transformación suele crecer hasta convertirse en lógica compleja: convertir un +texto en un registro estructurado, mapear códigos locales a elementos de datos +de DHIS2 o unificar una docena de formatos de fecha en uno solo. + +Hacer pruebas unitarias de esa lógica ayuda a comprobar que el código funciona +correctamente y a evitar errores cuando se modifique más adelante. + +:::info Requisitos + +Necesitas `@openfn/cli` v1.39.0 o posterior para compilar el código de tus jobs +para las pruebas. Comprueba tu versión con `openfn -v` y actualízala con +`npm install -g @openfn/cli`. + +También necesitas un proyecto de OpenFn descargado en tu computadora: + +```bash +openfn project pull +``` + +Así obtienes una carpeta con `openfn.yaml`, un directorio `workflows/` y un +archivo `.js` por cada step. Consulta [OpenFn Sync](/documentation/sync) para +saber cómo descargar proyectos, hacer checkout y desplegarlos. + +::: + +## Paso 1: exporta las funciones auxiliares que quieras probar {#step-1-export-any-helper-you-want-to-test} + +Las funciones que quieras probar tienen que estar exportadas: + +```js title="testable code" +export const FIELDS = ['id', 'name', 'dob']; + +export const parseSms = text => + Object.fromEntries(FIELDS.map((f, i) => [f, text.split('#')[i]])); + +fn(state => ({ ...state, data: state.data.messages.map(parseSms) })); +``` + +## Paso 2: compila tus workflows {#step-2-compile-your-workflows} + +Desde la raíz de tu proyecto (la carpeta que tiene `openfn.yaml`): + +```bash +openfn compile --exports-only +``` + +``` +[CLI] ✔ Compiled 1 step(s) to /path/to/project/dist +``` + +Los archivos compilados se guardan en `dist/`, con la misma estructura que tus +workflows. **Los archivos de salida usan la extensión `.mjs`.** Node siempre +trata los archivos `.mjs` como módulos ES, así que no necesitas +`"type": "module"` en tu `package.json` para que el código compilado se importe +sin problemas. + +:::warning No hagas commit de los archivos `.mjs` generados + +La CLI no agrega un `.gitignore` para el directorio compilado, así que agrégalo +tú antes de tu primer commit: + +```title=".gitignore" +dist/ +``` + +Los archivos `.mjs` son resultado de la compilación y salen por completo de tus +steps `.js`. Si los incluyes en el repositorio, cada edición te deja diffs +ruidosos y conflictos de merge, y `dist/` puede dejar de coincidir con +`workflows/`. + +::: + +Otras opciones útiles: + +```bash +# Write somewhere other than dist/ +openfn compile --exports-only -o workflows + +# Wipe the output folder first +openfn compile --exports-only --clean + +# Just one workflow, by name +openfn compile sms-parser --exports-only +``` + +Ejecuta `openfn compile --help` para ver la lista completa. + +También puedes definir la carpeta de salida de forma permanente en +`openfn.yaml`: + +```yaml title="openfn.yaml" +dirs: + workflows: workflows + compiled: workflows +``` + +## Paso 3: escribe una prueba {#step-3-write-a-test} + +Aquí recomendamos el ejecutor de pruebas integrado de Node porque no necesita +dependencias, pero nada de esto es exclusivo de Node. Puedes usar cualquier +ejecutor de pruebas que pueda importar un módulo ES. + +:::tip Ponle a tus archivos de prueba la extensión `.test.mjs` + +La salida compilada es `.mjs` y no necesita configuración. Pero tus archivos de +_prueba_ son cosa tuya: si les pones la extensión `.js` en un proyecto sin +`"type": "module"`, Node te avisará de que tiene que volver a analizarlos como +módulos ES. Si los llamas `.test.mjs`, evitas el aviso sin tocar tu +`package.json`. + +::: + + + + + ```js title="workflows/sms-parser/parse-message.js" + export const FIELDS = ['id', 'name', 'dob', 'weight']; + + export const parseSms = text => { + const parts = text.trim().split('#'); + return FIELDS.reduce((record, field, i) => { + record[field] = parts[i]?.trim() ?? null; + return record; + }, {}); + }; + + fn(state => ({ + ...state, + data: state.data.messages.map(parseSms), + })); + ``` + + + + + Después de `openfn compile --exports-only`: + + ```js title="dist/sms-parser/parse-message.mjs" + export const FIELDS = ['id', 'name', 'dob', 'weight']; + + export const parseSms = text => { + const parts = text.trim().split('#'); + return FIELDS.reduce((record, field, i) => { + record[field] = parts[i]?.trim() ?? null; + return record; + }, {}); + }; + ``` + La operación `fn(...)` desapareció. Las dos exportaciones se conservaron. + + + + Fíjate en la ruta del import: apunta a `dist/`, **no** a tu archivo fuente. + + ```js title="test/parse-message.test.mjs" + import { test } from 'node:test'; + import assert from 'node:assert/strict'; + + import { parseSms } from '../dist/sms-parser/parse-message.mjs'; + + test('parses a well-formed message into a record', () => { + assert.deepEqual(parseSms('P-001#Ada Lovelace#1815-12-10#3.2'), { + id: 'P-001', + name: 'Ada Lovelace', + dob: '1815-12-10', + weight: '3.2', + }); + }); + ``` + + + + +### Ejecutar la prueba {#running-the-test} + +```bash +openfn compile --exports-only && node --test +``` + +``` +✔ parses a well-formed message into a record (0.9ms) +✔ trims whitespace around each field (0.1ms) +✔ fills missing trailing fields with null (0.1ms) +ℹ tests 1 +ℹ pass 1 +ℹ fail 0 +``` + +## Paso 4: ejecuta las pruebas en modo watch {#step-4-running-test-in-watch-mode} + +Ejecuta el compilador en modo watch en una terminal: + +```bash +openfn compile --exports-only --watch +``` + +Y tu ejecutor de pruebas en modo watch en otra: + +```bash +node --test --watch +``` + +Ahora, cada vez que editas un step, este se vuelve a compilar, lo que cambia un +archivo en `dist/` y vuelve a ejecutar tus pruebas. + +## Páginas relacionadas {#related-pages} + +- [Compilación](/documentation/jobs/compilation): qué hace el compilador y por + qué +- [Buenas prácticas](/documentation/jobs/best-practices): cómo escribir código + de jobs que valga la pena probar +- [Uso básico de la CLI](/documentation/cli-usage): cómo ejecutar workflows en + tu computadora +- [OpenFn Sync](/documentation/sync): cómo descargar un proyecto para tener un + `openfn.yaml` que compilar diff --git a/i18n/es/docusaurus-plugin-content-docs/current/jobs/using-cursors.md b/i18n/es/docusaurus-plugin-content-docs/current/jobs/using-cursors.md new file mode 100644 index 000000000000..4054cb452c1f --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/jobs/using-cursors.md @@ -0,0 +1,205 @@ +--- +sidebar_label: Usar cursores +title: Usar cursores +translation_source_hash: da397a43bea2cd3ef29dc7ba2da3c9fecceeaf7e +translation_review_status: machine +--- + +## Usar cursores {#using-cursors} + +A veces conviene mantener una posición de cursor móvil sobre la fuente de datos +del backend. Por ejemplo, en un workflow con trigger cron, puedes usarla para +consultar en la base de datos los registros nuevos desde el último run. + +En un workflow cron, OpenFn pasa el state anterior al siguiente, así que el +state se conserva entre runs. Puedes aprovecharlo para retomar donde te +quedaste. + +Para facilitar el manejo del cursor, puedes usar la operación +[`cursor()`](/adaptors/packages/common-docs#cursor), que viene incluida en la +mayoría de los adaptors. + +
+Versiones compatibles + +La operación cursor se agregó a @openfn/language-common en la +versión 1.13.0 (publicada en abril de 2024). + +Los adaptors que usan common 1.12.0 o anterior no admiten la +operación cursor. Considera actualizar a la versión más reciente del adaptor +para aprovechar esta funcionalidad. + +
+ +### Definir el valor del cursor {#setting-the-cursor-value} + +Para usar un cursor desde una fecha fija, solo agrega una línea como esta al +principio de tu job: + +```js +cursor('2024-04-08T12:00:00.0000'); +``` + +Con un valor de texto como este, el cursor usará _siempre_ la fecha que +indicaste. + +Si usas un cursor de fecha, también puedes pasarle cadenas en lenguaje natural +como "now", "today", "yesterday", "24 hours ago" o "start" (es decir, la hora en +que empezó el job). + +:::tip Zonas horarias + +Las fechas relativas como "today" se convierten en un Date de JavaScript con la +configuración regional del sistema. + +Si usas la CLI, las horas se calculan con la hora local de tu sistema; si lo +ejecutas en Lightning, se usa la hora del sistema de Lightning (normalmente +UTC). + +La función cursor registra en el log la hora exacta que usa, con la zona +horaria. + +::: + +Para usar un cursor móvil o manual, deberías pasar el valor del cursor desde el +state. Quizás también quieras incluir un valor predeterminado: + +```js +cursor(state => state.cursor, { defaultValue: '2024-04-08T12:00:00.0000' }); +``` + +### Usar el cursor {#using-the-cursor} + +Para usar el cursor en tu job, solo usa `state.cursor` en tus consultas, como +cualquier otra propiedad del state. + +El uso cambia según el adaptor. Así podrías armar una URL con parámetros de +consulta usando el adaptor HTTP: + +```js +get(state => `/registrations?since=${state.cursor}`); +fn(/* do something good with your data */); +``` + +Esto lee el valor del cursor del objeto state, lo inserta en una cadena y lo +pasa a una consulta HTTP. + +O quizás quieras incluir el cursor en un objeto: + +```js +get('registrations', state => { + query: { + fromdate: state.cursor; + } +}); +``` + +El valor de un cursor puede ser cualquier cosa: una cadena, un Date, un número +de página, un objeto o lo que prefieras. + +Quizás quieras avanzar el cursor al final de un job, para dejarlo listo para el +siguiente run: + +```js +cursor(state => state.cursor, { defaultValue: 'today' }); +get(`/registrations?since={date.cursor}`); +fn(/* do something good with your data */); +cursor('now'); +``` + +### Cursores manuales {#manual-cursors} + +A menudo conviene definir la posición del cursor a mano, normalmente al probar o +depurar. Quizás el run de ayer falló y quieres repetirlo, o estás probando una +funcionalidad nueva y quieres experimentar con distintos cursores. + +Para hacerlo, define un valor de cursor en el state de entrada, así: + +```js +{ + "cursor": "today", +} +``` + +Puedes hacerlo al iniciar un run manual en el +[Job Inspector](/build/steps/step-editor.md) de la plataforma, o pasando el +state como entrada a la CLI: + +```bash +$ openfn job.js -s state.json -a http +``` + +
+Cursores manuales en v1 + +La plataforma v1 no permite definir libremente el state de entrada, así que +definir un cursor manual es un poco más difícil. + +Tienes que escribir el cursor manual directamente en el run para que se ignore +el cursor del state: + +```js +cursor('2024-03-12'); +``` + +Deberías comentar esta línea en los runs de producción. + +También puedes usar la opción defaultValue. Funciona siempre que ejecutes sin +ningún state inicial: + +```js +cursor(state => state.cursor, { defaultValue: '2024-03-12' }); +``` + +
+ +### Opciones del cursor {#cursor-options} + +El segundo argumento de `cursor()` es un objeto de opciones. Puedes usarlo para +definir el `defaultValue` o la `key` que debe usar el cursor (por defecto, +`cursor`): + +```js +cursor(state => state.cursor, { defaultValue: '2024-03-12', key: 'page' }); +``` + +### Dar formato al valor {#formatting-the-value} + +Si usas un servicio que no sigue los formatos de fecha estándar, o quieres +convertir varios formatos de entrada a un estándar común, puedes usar la opción +`format`. + +`format` recibe una función que toma como argumento el valor actual del cursor y +devuelve un valor con formato o actualizado. Se llama justo antes de asignar el +cursor al state. + +Por ejemplo, para usar un Date de JavaScript como cursor: + +```js +cursor('today', { format: c => new Date(c) }); +``` + +La función de formato se ejecuta después de procesar el lenguaje natural, así +que puedes interceptar el valor y convertirlo en lo que necesites. + +Puedes combinarla con +[`dateFns.format`](https://date-fns.org/v3.6.0/docs/format) para usar una marca +de tiempo personalizada: + +```js +cursor('today', { format: c => dateFns.format(new Date(c), 'dd/mm/yyyy') }); +``` + +Puedes agregar toda la lógica que quieras a la función de formato; es una +función normal de JavaScript: + +```js +cursor('today', { + format: c => { + if (typeof c === 'number') { + return { page: c, count: 20 }; + } + return c; + }, +}); +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/keyboard-shortcuts.md b/i18n/es/docusaurus-plugin-content-docs/current/keyboard-shortcuts.md new file mode 100644 index 000000000000..642c08630624 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/keyboard-shortcuts.md @@ -0,0 +1,44 @@ +--- +title: Atajos de teclado +keywords: [keystrokes, keyboard shortcuts, shortcuts] +translation_source_hash: 5d12e36efd8be3046bc306dc66a9aa22d367c006 +translation_review_status: machine +--- + +Los atajos de teclado (combinaciones de teclas) te permiten realizar acciones +comunes sin quitar las manos del teclado. 🤓 + +## Atajos de la plataforma {#platform-shortcuts} + +| Comando | Disponibilidad | Mac | Linux/Windows | Notas | +| ----------------------------------------------- | -------------------- | ---------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | +| **Cambiar** de proyecto | En cualquier lugar | `⌘+p` | `Ctrl+p` | Abre el selector de proyectos para cambiar entre tus proyectos (es decir, espacios de trabajo seguros) | +| **Expandir o contraer** el menú lateral | En cualquier lugar | `⌘+m` | `Ctrl+m` | | +| **Mostrar u ocultar** el AI Assistant | Canvas, IDE | `⌘+k` | `Ctrl+k` | | +| **Guardar** el workflow | Canvas, IDE | `⌘+s` | `Ctrl+s` | | +| **Guardar** y sincronizar el workflow | Canvas, IDE | `⌘+Shift+s` | `Ctrl+Shift+s` | Al elegir sincronizar, se te pedirá que escribas un mensaje de commit o que uses el mensaje predeterminado. | +| **Ejecutar** | Canvas, IDE | `⌘+Return` | `Ctrl+Enter` | Guarda tu workflow y lo ejecuta desde el step actual, con el comportamiento predeterminado de agrupación en work orders.\* | +| **Ejecutar** _(acción alternativa)_ | Canvas, IDE | `⌘+Shift+Return` | `Ctrl+Shift+Enter` | Guarda tu workflow y abre el diálogo de entrada personalizada del run o (si ya está abierto) crea una nueva work order desde el step actual. | +| **Mostrar u ocultar** el IDE (editor de código) | Canvas, IDE | `⌘+e` | `Ctrl+e` | Abre el editor de código a pantalla completa para el job seleccionado. | +| **Mostrar u ocultar** Run History | Canvas, IDE | `⌘+h` | `Ctrl+h` | No está disponible al crear un workflow nuevo. | +| **Cerrar** el panel o el IDE | Canvas, IDE | `Escape` | `Escape` | Cierra el Inspector, el IDE o el panel del run, si están abiertos. | +| **Mostrar u ocultar** el panel Templates | Creación de workflow | `⌘+/` | `Ctrl+/` | Solo está disponible al crear un workflow nuevo. | +| **Mostrar u ocultar** el panel Import | Creación de workflow | `⌘+\` | `Ctrl+\` | Solo está disponible al crear un workflow nuevo. Permite importar desde YAML. | + +\*Si estás viendo un run existente y creas un run nuevo desde el Canvas o el +IDE, ese run se asocia a la work order existente: este es el comportamiento +predeterminado. (Piénsalo como un "reintento"). A veces, por motivos de +auditoría, conviene crear una work order completamente nueva. Para hacerlo, usa +el botón de ejecución alternativa. + +## Algunos atajos del editor {#selected-editor-shortcuts} + +Consulta la paleta de comandos (haz clic derecho o presiona `F1`) para ver la +lista completa. Para usar estos atajos, tienes que hacer clic dentro de un +editor concreto: el IDE tiene varios editores. + +| Comando | Disponibilidad | Mac | Linux/Windows | +| ----------------------------- | --------------- | ---------------- | ------------- | +| Ver los comandos del editor | IDE, Run Viewer | `F1` | `F1` | +| Formatear el código | IDE | `Shift+Option+F` | `Shift+Alt+F` | +| Comentar o descomentar código | IDE | `⌘+/` | `Ctrl+/` | diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/collaboration.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/collaboration.md new file mode 100644 index 000000000000..323dd8dd3e63 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/collaboration.md @@ -0,0 +1,80 @@ +--- +title: Colaboración +sidebar_label: Colaboración +slug: /collaboration +translation_source_hash: e1e45b885085466371f4e864cd0b6210f1e153d5 +translation_review_status: machine +--- + +OpenFn permite que usuarios técnicos y no técnicos colaboren de forma eficaz y +se mantengan alineados al diseñar y gestionar los workflows de un proyecto. Esto +es posible gracias al Canvas, un editor visual de workflows, y a otras +funcionalidades de colaboración, como el control de versiones, la incorporación +de colaboradores y el uso compartido de credenciales, entre otras. Esta guía te +explica cómo gestionar los colaboradores de un proyecto. + +### ¿Quiénes son los colaboradores de un proyecto? {#who-are-project-collaborators} + +Un **colaborador de proyecto** es cualquier persona que tiene permisos +administrativos de edición o de visualización en un proyecto de OpenFn. A cada +colaborador se le asigna UNO de los cuatro roles principales en un proyecto al +que puede acceder, como se resume en la siguiente tabla: + +| Rol | Descripción | +| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Owner | El usuario que creó el proyecto | +| Admin | Un usuario que no es el propietario del proyecto, pero tiene acceso sin restricciones al proyecto y a sus workflows. También tiene permiso para agregar otros colaboradores al proyecto. | +| Editor | Un usuario con acceso a un proyecto que puede editar los workflows y la configuración del proyecto. El rol Editor es más limitado que el rol Admin | +| Viewer | Un usuario con acceso a un proyecto, pero limitado a ver la configuración y los artefactos del proyecto. | + +Puedes obtener más información sobre los permisos de cada rol +[aquí](/manage-projects/user-roles-permissions.md). + +### Agregar colaboradores al proyecto {#add-project-collaborators} + +Un usuario con rol Owner, Admin o Editor en un proyecto puede invitar a nuevos +colaboradores a su proyecto de OpenFn desde la página `Settings` del proyecto. + +Para agregar como colaborador a un usuario que ya tiene cuenta en OpenFn: + +1. Ve a la página `Settings` del proyecto y abre la pestaña `Collaboration` +2. Haz clic en el botón `Add Collaborator(s)` +3. Escribe el correo electrónico del usuario y selecciona el `Role` (Viewer, + Editor o Admin). +4. Agrega más colaboradores con el botón `Add Additional Collaborator`. También + puedes quitar a uno de los colaboradores con el botón de menos (-). +5. Haz clic en el botón `Save Collaborator` para guardar los cambios. + +Si alguno de los correos electrónicos que escribiste no tiene una cuenta de +OpenFn asociada, se te pedirá que autorices a OpenFn a crearle una cuenta y +enviarle una invitación a tu proyecto. Haz clic en `Invite new user` para +continuar con la invitación. + +![Colaboración](/img/collaboration.webp) + +![Agregar colaborador](/img/add_collab.webp) + +![Invitar a nuevos usuarios](/img/invite-new-users.webp) + +:::note + +Un proyecto tiene exactamente _un_ Owner, y no puedes asignar el rol Owner a +otro colaborador. Si necesitas cambiar el Owner del proyecto, contacta a tu +superadministrador o escribe a [support@openfn.org](mailto:support@openfn.org). + +::: + +### Quitar un colaborador {#removing-a-collaborator} + +Para quitar a un colaborador de un proyecto, un Owner o un Admin puede hacer +clic en el botón `Remove Collaborator` de la página `Collaboration` y confirmar +la eliminación en la ventana emergente. No se puede quitar al Owner de un +proyecto. + +:::tip + +En la página de colaboradores del proyecto también puedes configurar las alertas +de fallos y los resúmenes de tus proyectos. Obtén más información +[en esta guía](/manage-projects/notifications.md). + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/io-data-storage.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/io-data-storage.md new file mode 100644 index 000000000000..93d4f1088406 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/io-data-storage.md @@ -0,0 +1,105 @@ +--- +title: Almacenamiento de datos +translation_source_hash: 968a9459539d0d27aef3fa4496b6c80ef163d706 +translation_review_status: machine +--- + +En la sección "Data Storage" de los `Settings` de tu proyecto, puedes configurar +qué quieres que haga OpenFn con los _datos_ reales (`Inputs` y `Outputs`) que +procesan (o producen) los runs de tus workflows. + +### ¿Por qué guardaría los datos de entrada y salida junto con los logs de los runs? {#why-would-i-store-inputoutput-data-along-with-run-logs} + +Puedes configurar OpenFn para que guarde datos temporalmente (por ejemplo, +formularios obtenidos de la aplicación móvil de CommCare) y así poder +diagnosticar y corregir fácilmente las transacciones cuando haya errores (por +ejemplo, si el sistema DHIS2 de destino no está disponible o si una restricción +o validación de la base de datos bloquea una importación de datos). El +administrador de la instancia de OpenFn define un período de retención de datos +predeterminado, pero puede modificarse según los requisitos de cada proyecto. + +Una de las funcionalidades más potentes de la plataforma es la posibilidad de +"reproducir" work orders. Si tienes un workflow de varios steps (por ejemplo, +obtener datos de una base de datos, transformar y mapear los datos, e +importarlos a tu sistema de información de salud), este almacenamiento temporal +de datos permite a los administradores de OpenFn diagnosticar rápidamente las +work orders fallidas y "reintentar" el workflow desde el step que falló, en +lugar de volver a ejecutarlo desde el principio. Así, los administradores pueden +reprocesar las work orders fallidas sin tener que obtener (o volver a enviar) +los datos (entradas) desde un sistema de origen. + +### ¿Por qué elegiría _NO_ guardar los datos de entrada y salida? {#why-would-i-choose-to-_not_-store-inputoutput-data} + +Algunos de nuestros usuarios procesan datos extremadamente sensibles (como +historias clínicas) y quizás quieran asegurarse de que, una vez ejecutado un +workflow, no queden datos de pacientes en los servidores de OpenFn. + +Habilitar esta funcionalidad de "persistencia cero" ("zero-persistence") para +los datos de entrada y salida es una opción atractiva para quienes quieren usar +OpenFn en la nube, pero les preocupa la soberanía de los datos. + +:::tip + +Consulta la página de documentación sobre +[seguridad y cumplimiento](/get-started/security-compliance.md) para saber más +sobre el almacenamiento de datos y las arquitecturas de soluciones que se basan +en pipelines de datos de OpenFn con "persistencia cero". + +::: + +### Exportar el historial {#export-history} + +También puedes exportar todas las work orders de un proyecto y sus artefactos +asociados (runs, steps, runsteps y dataclips de entrada y salida). La +exportación del historial de work orders se gestiona a nivel de proyecto y está +disponible para todos los colaboradores del proyecto (viewer, editor, admin, +owner). + +#### Cómo exportar el historial de work orders {#how-to-export-work-order-history} + +Para exportar el historial de work orders de tu proyecto, abre el proyecto y haz +clic en `History` en el menú lateral. En la página History, desplázate hasta el +final de la tabla del historial de work orders y haz clic en el ícono de la nube +(mira la imagen de abajo). + +![Página History](/img/history_page_cloud.webp) + +Al hacer clic en el ícono de descarga, aparece una ventana modal para confirmar +la exportación. Si confirmas, se inicia un proceso en segundo plano para la +exportación. + +![Confirmar la exportación](/img/confirm_export.webp) + +Cuando termine la exportación, se enviará un correo electrónico a la dirección +asociada a tu usuario de OpenFn. + +:::info PARA DESPLIEGUES LOCALES + +En los despliegues locales, OpenFn usa Swoosh como servicio de buzón para +desarrollo, y puedes acceder al buzón en http://localhost:4000/dev/mailbox. +Puedes cambiar localhost:4000 por el puerto donde se aloja tu instancia de +OpenFn. + +::: + +#### Gestionar las exportaciones {#managing-exports} + +Puedes ver todas las exportaciones del historial en la página `History Exports` +de la configuración del proyecto. Haz clic en `Settings` en el menú lateral y +luego en `History Exports` para ver la lista de exportaciones de work orders de +tu proyecto. + +En la página `History Exports` verás la lista de exportaciones, con tu solicitud +más reciente y las anteriores, junto con otros datos como el nombre del archivo, +la fecha de exportación, el usuario que la solicitó y el estado. + +![Lista de exportaciones del historial](/img/history_exports_page.webp) + +:::caution Configurar el almacenamiento de las exportaciones + +En los despliegues locales, los administradores de la instancia de OpenFn pueden +configurar dónde se guardan las exportaciones de work orders. Actualmente, +OpenFn admite el almacenamiento local y Google Cloud Storage como destinos para +exportar work orders. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/link-to-gh.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/link-to-gh.md new file mode 100644 index 000000000000..de677ffd414a --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/link-to-gh.md @@ -0,0 +1,334 @@ +--- +title: GitHub Sync +sidebar_label: GitHub Sync +slug: /link-to-GitHub +translation_source_hash: 4ae70e41e6cd18383fc6ee3bdcde51b4d49be923 +translation_review_status: machine +--- + +GitHub Sync permite una sincronización bidireccional entre un proyecto de OpenFn +y tu repositorio de GitHub. + +Esto significa que los cambios que hagas en un proyecto en OpenFn pueden +guardarse como commits en tu repositorio de GitHub, y los commits que subas a +GitHub pueden actualizar el proyecto en tu aplicación de OpenFn. + +:::info Para usuarios de OpenFn en la nube + +GitHub Sync solo está disponible en proyectos con planes Core, Growth, Scale o +Custom. + +::: + +### Configurar tu proyecto para usar GitHub Sync {#configuring-your-project-to-use-github-sync} + +Puedes configurar tus proyectos para que accedan a uno o más repositorios de +GitHub. Tienes que tener acceso de administrador al repositorio de GitHub para +poder instalar la aplicación de OpenFn. + +Para configurar tu proyecto para usar la sincronización con GitHub, sigue estos +pasos: + +1. Ve a `Project Settings > Sync to GitHub`. + +2. Si todavía no conectaste tu cuenta de usuario de OpenFn a GitHub, hazlo con + el botón **"Connect your OpenFn account to GitHub"**. + +![Configurar](/img/connect-account-to-github.webp) + +3. Elige qué instalación de GitHub usar para tu proyecto, o sigue el consejo de + abajo para actualizar tus instalaciones. + + :::tip + + Si no ves ninguna instalación, o si las que hay no tienen acceso a los + repositorios que quieres, haz clic en el enlace **"Create/update GitHub + installations or modify permissions"** para gestionar la instalación de + OpenFn en GitHub. Para ello tendrás que darle permisos a la aplicación de + OpenFn para acceder a tu cuenta y tu repositorio de GitHub. Si necesitas + ayuda, consulta + [Gestionar los permisos de GitHub](#managing-github-permissions). + + Cuando termines, puedes volver aquí y actualizar las listas con el botón 🔄 + que está junto a las listas desplegables. + + ::: + +4. Elige el repositorio y la rama a los que quieres conectar tu proyecto. + +![Configurar](/img/github-options.webp) + +5. **_Opcionalmente_**, si _primero quieres sincronizar de GitHub a OpenFn y ya + tienes un archivo de configuración_, agrega la ruta de un archivo + `config.json` de un proyecto existente. + + :::caution La mayoría de los usuarios deja "Path to config" en blanco. + + Esta funcionalidad avanzada te permite conectarte a un repositorio de GitHub + que _ya_ tiene un `project.yaml` y un `config.json` de OpenFn. (La mayoría de + las personas puede saltarse este paso.) Es útil cuando quieres que la primera + sincronización traiga datos de GitHub a OpenFn. La mayoría de los usuarios + prefiere que la primera sincronización salga _de_ OpenFn y que la aplicación + cree por ellos los archivos `config.json` y `project.yaml` necesarios. + + ::: + +6. Elige la **dirección** de la _primera_ sincronización. Es decir, cuando se + establezca esta conexión, ¿quieres que la integración _primero_ envíe una + copia de tu proyecto de OpenFn a GitHub, o que _primero_ sobrescriba tu + proyecto de OpenFn con un `project.yaml` que ya está en GitHub? + + :::warning Elegir desplegar _primero_ de "GitHub to OpenFn" es destructivo + + De forma predeterminada, tomamos lo que tienes en tu proyecto actual de + OpenFn y lo enviamos a GitHub para empezar el control de versiones. Si en + cambio eliges tomar un archivo `project.yaml` de GitHub y sobrescribir tu + proyecto actual de OpenFn, no podrás recuperar tus workflows existentes en + OpenFn. Esta funcionalidad cubre ciertos casos de uso avanzados y, a menos + que sepas lo que estás haciendo, deberías empezar sincronizando de "OpenFn to + GitHub". + + ::: + +7. Haz clic en **"Connect Branch & Initiate First Sync"** para terminar. Una vez + hecho esto, puedes ir a GitHub (con el enlace que se te da) para ver tu + proyecto de OpenFn como código (y empezar a trabajar con él). + +## Gestionar los permisos de GitHub {#managing-github-permissions} + +El acceso de la aplicación de OpenFn a tus repositorios de GitHub se concede _en +GitHub_, no en OpenFn. En la interfaz te damos un enlace para instalar y +gestionar estos permisos. Después de hacer clic en ese enlace, puedes seguir +estos pasos: + +1. Haz clic en **"Configure"** o **"Install"**. + +![Configurar](/img/lightning_gh_configure.webp) + +2. Luego selecciona la cuenta de GitHub propietaria del repositorio al que + quieres conectarte. + +![Instalar](/img/lightning_gh_install_openfn.webp) + +3. Selecciona el repositorio que quieres sincronizar y haz clic en **"Save"**. + +![Permisos](/img/lightning_gh_permissions.webp) + +4. Cuando termines de hacer cambios en GitHub, vuelve a OpenFn y actualiza las + listas de conexiones con el botón 🔄 que está junto a la lista desplegable de + instalaciones disponibles. + +## Usar el control de versiones y gestionar los cambios {#using-version-control--managing-changes} + +La funcionalidad `Sync to GitHub` usa GitHub Actions para desplegar +automáticamente (después de un commit en GitHub) o traer (cuando se hace clic en +el botón **"Initiate Sync to Branch"** en OpenFn) los cambios del proyecto, y +así mantener un repositorio sincronizado con tu proyecto de OpenFn. + +### Sincronizar de OpenFn a GitHub {#sync-from-openfn-to-github} + +Esta sincronización envía a GitHub los cambios de tu proyecto de OpenFn. Esta +operación de sincronización dispara un workflow de acción `openfn pull` en tu +repositorio de GitHub conectado, que trae la configuración más reciente de la +aplicación de OpenFn y la guarda como código en el archivo `project.yaml` de tu +repositorio. + +:::info + +Tu proyecto de OpenFn puede representarse como código y empaquetarse como +project.yaml, lo que se llama la especificación del proyecto (project spec). +Consulta la [documentación sobre portabilidad](/deploy/portability.md) para +saber más. + +::: + +Una vez que hayas configurado correctamente la conexión de tu proyecto con +GitHub como se explica [arriba](#managing-github-permissions), puedes iniciar +las siguientes sincronizaciones desde el Canvas, desde el Inspector o desde la +página de control de versiones de la configuración del proyecto. + +Para iniciar una sincronización desde el Canvas o el Inspector, presiona +`Ctrl+Shift+s` (o `⌘+Shift+s` en Mac; consulta los +[atajos de teclado](/keyboard-shortcuts.md)). También puedes hacer clic en el +ícono desplegable junto al botón de guardar y seleccionar `Save & Sync`. Al +hacer clic en Save & Sync, verás una ventana modal de confirmación con la opción +de personalizar el mensaje del commit. + +![Iniciar Save & Sync](/img/save-and-sync.webp) + +:::info La sincronización es una acción "a nivel de proyecto" + +Cuando ejecutas `Save & Sync` en un workflow, tus cambios nuevos y los cambios +_anteriores_ sin commit (si los hay) en los recursos de tu proyecto (incluidos +otros workflows) se guardan como commit en GitHub. Es decir, si tú u otra +persona hicieron cambios sin commit en otros workflows del proyecto, también +aparecerán en esa sincronización. + +::: + +Para sincronizar tu proyecto con GitHub desde la configuración del proyecto: + +1. Ve al proyecto donde editaste tus workflows y luego a la página + `Project Settings` +2. Desde la configuración del proyecto, ve a la página `Version Control` + haciendo clic en `Sync to GitHub` +3. Haz clic en el botón `Initiate Sync to Branch` para disparar una + sincronización con el repositorio de GitHub conectado + +![Iniciar la sincronización con GitHub](/img/sync_to_github.webp) + +### Sincronizar de GitHub a OpenFn {#sync-from-github-to-openfn} + +Usa este método de sincronización cuando quieras traer a OpenFn una versión de +tu proyecto desde GitHub. Cuando se dispara esta sincronización, se ejecuta la +acción `openfn-deploy` en GitHub y la especificación de tu proyecto _(el archivo +que termina en `.yaml`)_ se despliega automáticamente en OpenFn. + +:::tip Qué tener en cuenta al sincronizar cambios de GitHub a OpenFn + +Desde la v2.7.19, las acciones de despliegue y pull de OpenFn admiten rutas +relativas en la especificación del proyecto. Por eso, los proyectos cuya +estructura de directorios usa rutas relativas para el código de los jobs en la +especificación del proyecto se empaquetan y despliegan automáticamente, sin que +tengas que copiar los cambios a la especificación del proyecto. Este nuevo +enfoque da a los desarrolladores más flexibilidad para gestionar mejor el código +de sus jobs en archivos individuales, en lugar de tener todo el código en el +archivo `projectSpec.yaml`. + +Encontrarás más información sobre las rutas relativas y la estructura de +directorios en la +[documentación sobre portabilidad](/deploy/portability-v3.md#directory-structure). + +::: + +### Usar Sync v2 {#using-sync-v2} + +De forma predeterminada, GitHub Sync usa la estructura de carpetas antigua para +representar tu proyecto en GitHub. Esa estructura de carpetas se explica más +abajo: crea los archivos `config.json`, `state.json` y `project.yaml` para +representar tu proyecto en tu repositorio de git. + +En su lugar, puedes elegir el formato de sincronización v2, que se describe en +las páginas de [Sync](/build-for-developers/cli-sync.md). Este formato "expande" +automáticamente tus workflows y steps en archivos fáciles de leer y escribir, y +ofrece una experiencia mucho mejor para los desarrolladores. + +Este estilo v2 pronto será la forma predeterminada de sincronizar proyectos. + +También puedes crear un archivo `openfn.yaml` vacío en un repositorio ya +conectado, y la siguiente sincronización generará la estructura de archivos v2. + +:::warning + +En Sync v1, puedes tener varias sincronizaciones bidireccionales en la misma +rama de un mismo repositorio. Esto es así porque cada proyecto crea su propio +conjunto de artefactos de sincronización (config.json, project.yaml y +state.json). Normalmente esto se hace para sincronizar tus proyectos de +producción y de staging, o varios sandboxes, con el mismo repositorio de GitHub. + +Esto no funciona con el nuevo protocolo de sincronización, porque la nueva +sincronización comparte una carpeta `workflows`. Así que cada vez que GitHub +trae los cambios de tu proyecto, sobrescribe `workflows` y borra el estado de +tus otros proyectos. + +Para hacer esto en Sync v2, puedes: + +- Mantener una sincronización bidireccional por rama. Cada sandbox mantiene su + propio GitHub Sync con una rama distinta de tu repositorio. Esto funciona muy + bien, porque puedes comparar las diferencias entre tu sandbox y el proyecto + principal comparando las ramas en git. +- Conectar muchos proyectos a una rama, siempre que solo se sincronicen en un + sentido. Esto funciona en un entorno de producción donde un proyecto se + replica en varios despliegues, de modo que un commit en GitHub dispara una + actualización en todos los proyectos conectados. Funciona siempre que puedas + garantizar que ningún usuario hará Save & Sync desde los proyectos de + producción. + +::: + +## ¿Qué hay en tu repositorio de GitHub? {#what-is-in-your-github-repository} + +:::info + +Esta documentación describe el formato antiguo de GitHub Sync. El formato más +reciente se describe en las páginas de +[CLI Sync](/build-for-developers/cli-sync.md) y pronto se usará de forma +predeterminada. + +::: + +Cuando inicias una conexión entre OpenFn y tu repositorio de GitHub, se crea +automáticamente en la rama que indicaste un archivo config.json, que contiene +una referencia a los archivos de especificación y de estado de tu proyecto, y el +endpoint de tu despliegue de OpenFn. De forma predeterminada, OpenFn nombra +todos tus archivos con el UUID de tu proyecto en OpenFn, así que verás archivos +como estos: + +```json +{ + "endpoint": "https://app.openfn.org", + "specPath": "openfn-fdfdf286-aa8e-4c9e-a1d2-89c1e6928a2a-spec.yaml", + "statePath": "openfn-fdfdf286-aa8e-4c9e-a1d2-89c1e6928a2a-state.json" +} +``` + +Puedes editar el archivo config.json para adaptarlo a tu estructura de carpetas, +siempre que apunte a la especificación, el estado y el endpoint de OpenFn +correctos. Abajo tienes un ejemplo de archivo config.json con un nombre +personalizado para la especificación y el estado del proyecto. + +```json +{ + "endpoint": "https://app.openfn.org", + "statePath": "./custom-name-for-project-state.json", + "specPath": "./custom-name-for-project-spec.yaml" +} +``` + +## Solución de problemas {#troubleshooting} + +### Error de GitHub Sync: Unexpected inputs provided: ["snapshots"] {#github-sync-error-unexpected-inputs-provided-snapshots} + +Si instalaste GitHub Sync antes del 17 de julio de 2024, quizás tengas que +actualizar tu archivo `.github/workflows/openfn-pull.yml` para que quede así: + +``` +on: + workflow_dispatch: + inputs: + projectId: + description: 'OpenFN Project ID' + required: true + apiSecretName: + description: 'OpenFN API Key secret name i.e OPENFN_project_API_KEY' + required: true + pathToConfig: + description: 'Path to config.json' + required: true + branch: + description: 'Branch to commit the project state and spec' + required: true + commitMessage: + description: 'Commit message for project state and spec' + required: true + snapshots: + description: 'IDs of snapshots separated by spaces' + required: false + +jobs: + pull-from-lightning: + runs-on: ubuntu-latest + permissions: + contents: write + name: A job to pull changes from Lightning + steps: + - name: openfn pull and commit + uses: openfn/cli-pull-action@v1.1.0 + with: + secret_input: ${{ secrets[inputs.apiSecretName] }} + project_id_input: ${{ inputs.projectId }} + config_path_input: ${{ inputs.pathToConfig }} + branch_input: ${{ inputs.branch }} + commit_message_input: ${{ inputs.commitMessage }} + snapshots_input: ${{ inputs.snapshots }} +``` diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/manage-credentials.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/manage-credentials.md new file mode 100644 index 000000000000..ca36080ead89 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/manage-credentials.md @@ -0,0 +1,189 @@ +--- +title: Gestionar credenciales +translation_source_hash: 7eb4c7e31c608628f42b86d45097f9e1d16c88d0 +translation_review_status: machine +--- + +Puedes ver las credenciales relacionadas con un proyecto en la página +`Settings > Credentials` del proyecto. En este artículo aprenderás a gestionar +las credenciales relacionadas con un proyecto. + +## Ver todas las credenciales del proyecto {#view-all-project-credentials} + +En la página `Credentials` puedes ver una lista de todas las credenciales, con +su nombre, tipo y propietario, y si son para un entorno de producción. + +![Resumen de credenciales](/img/lightning_credentials_overview.webp) + +:::info Ver los secretos de las credenciales + +Todos los colaboradores del proyecto pueden ver el nombre, el tipo y el +propietario de una credencial, pero solo su propietario puede ver los secretos +(nombre de usuario, contraseña, etc.). + +::: + +## Crear una credencial nueva {#create-a-new-credential} + +1. Haz clic en el botón `New Credential` y elige el tipo de aplicación que + quieres conectar. +2. Si tu aplicación no aparece en la lista, elige "Raw JSON" para crear tu + propia credencial personalizada o entrada de "configuración". Por ejemplo: + +```json +{ "loginUrl": "https://random-app.com", "username": "test", "password": "pwd" } +``` + +![Tipo de credencial](/img/lightning_choose_cred_type.webp) + +3. Haz clic en `Configure Credentials` y agrega los datos de autenticación de tu + aplicación. El formulario de la credencial indica qué campos son + obligatorios. + +![Agregar credencial](/img/lightning_add_cred.webp) + +:::tip ¿No sabes cómo completar todos los datos de la credencial? + +Si al crear una credencial nueva no sabes qué piden algunos campos (por ejemplo, +"security token"), ve a la página de documentación del [adaptor](/adaptors) +correspondiente para saber más y consulta su "configuration schema", o pregunta +en la [comunidad](https://community.openfn.org). + +::: + +4. Haz clic en `Save` y la verás en la lista de tu página `Credentials`. Ya + puedes usarla en todo el proyecto al crear y ejecutar workflows. + +![Credencial nueva lista](/img/lightning_new_cred_ready.webp) + +## Credenciales de Keychain (autenticación variable) {#keychain-credentials-variable-auth} + +Las credenciales de Keychain permiten que un mismo job use varias credenciales. + +Funcionan inspeccionando los datos del state del job en tiempo de ejecución (es +decir, state.data) y buscando el valor de un identificador predeterminado. Según +ese valor, presente en los datos de un mensaje de origen concreto, por ejemplo, +se selecciona y se aplica otra credencial para ese run del job. + +Imagina que tienes 2 credenciales en tu proyecto: + +1. Taylor’s Login, External ID: abc123, Body: + `{ username: “tay”, password: “shhhhh” }` + +2. Roina’s Login, External ID: def456, Body: + `{ username: “ro”, password: “veryshh” }` + +Y un job que usa una "Keychain Credential" con la ruta `$.data.myId`. + +Si un job de tu workflow usa la "Keychain Credential" y el dataclip inicial de +un run es así: + +```json +{ + "data": { + "content": "Hello world", + "myId": "abc123" + } +} +``` + +La credencial de Keychain buscará abc123 en las credenciales de tu proyecto y le +pasará esos secretos al mismo job. Es decir, el job se ejecutará con la +credencial "Taylor’s Login". + +Si se ejecuta otro run y su dataclip inicial es: + +```json +{ + "data": { + "content": "Goodbye!", + "myId": "def456" + } +} +``` + +El mismo job se ejecutará con la credencial "Roina’s Login". + +:::info Notas y limitaciones + +Como los secretos de las credenciales se obtienen al inicio de un run (no al +inicio de un step), actualmente no es posible resolver credenciales de Keychain +a partir de datos que se agregan al state más adelante en el run. Es decir, los +datos tienen que estar en el dataclip de entrada de todo el run, no en el +dataclip de entrada del step que usa la credencial de Keychain. + +::: + +### Crear una credencial de Keychain {#create-a-keychain-credential} + +1. En la página `Credentials` de la configuración del proyecto, haz clic en el + ícono desplegable del botón `Add New` y selecciona la opción Keychain: + + ![](/img/keychain_credential_dropdown.webp) + +2. Ponle un nombre a tu credencial de Keychain y asígnale una expresión + JSONPath. También puedes seleccionar una credencial predeterminada para usar + cuando la expresión JSONPath no encuentre coincidencias: + + ![](/img/keychain_modal.webp) + +3. Asigna un ID externo al que pueda acceder tu Keychain, creando una credencial + nueva o editando una existente: + + ![](/img/assign_externalID.webp) + +4. Ahora, en un job de tu workflow, puedes seleccionar y usar una credencial de + Keychain: + + ![](/img/keychain_selection.webp) + +5. Ya puedes hacer referencia a tu Keychain en tu entrada para usarla: + + ![](/img/keychain_input.webp) + +## Compartir credenciales {#share-credentials} + +Si eres propietario de una credencial, puedes elegir qué proyectos tienen acceso +a ella. Para actualizar los proyectos con los que compartes tu credencial, sigue +los pasos de la +[página de documentación de credenciales de usuario](/manage-users/user-credentials.md). + +## Credenciales `Raw JSON` {#raw-json-credentials} + +Las credenciales raw son documentos JSON válidos que se pasan al state del job +en tiempo de ejecución. Ten en cuenta que los propietarios de estas credenciales +pueden verlas completas y sin cifrar. + +Las credenciales raw funcionan con cualquier adaptor, siempre que la credencial +especifique las claves de `configuration` que ese adaptor exige (por ejemplo, +`baseUrl`). Consulta la documentación del "configuration schema" de cada adaptor +para ver qué se necesita para esa aplicación. + +:::info Usa `Raw JSON` para entradas personalizadas en la credencial + +Usa el tipo de credencial `Raw JSON` si quieres guardar secretos que no son +entradas estándar del formulario de credencial de un adaptor. Por ejemplo, si tu +API REST necesita un `client_id` en lugar de un `username`, tu esquema de +`configuration` podría parecerse al fragmento de código de abajo. Como +`client_id` no es una opción del formulario de credencial `Http` predeterminado, +puedes crear tu propia credencial personalizada con el tipo `Raw JSON`. + +::: + +Ejemplo del cuerpo o `configuration` de una credencial Raw JSON: + +```json +{ + "baseUrl": "https://myapp.com/api", + "client_id": "test-j01", + "password": "testing123", + "customInput": "whateverYouWant" +} +``` + +## Seguridad de las credenciales {#credentials-security} + +Todas las credenciales se guardan cifradas en reposo, y solo sus propietarios +pueden ver los secretos. Consulta la +[documentación de seguridad](/get-started/security-compliance.md) de OpenFn para +más información. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/notifications.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/notifications.md new file mode 100644 index 000000000000..cbd586906858 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/notifications.md @@ -0,0 +1,46 @@ +--- +title: Notificaciones de fallos y resúmenes +sidebar_label: Notificaciones por correo electrónico +slug: /notifications +translation_source_hash: 388be709d62844f7f65a6592d669a5e1b49983df +translation_review_status: machine +--- + +Si quieres recibir `Notifications` por correo electrónico cuando falla un run de +un workflow, o `Digests` por correo con un resumen de la actividad de tu +proyecto, sigue leyendo para saber cómo configurar tu proyecto. + +Este artículo te explica cómo configurar las notificaciones por correo +electrónico para hacer seguimiento de tus workflows. + +### Alertas de fallos {#failure-alerts} + +En `Project Settings > Collaboration` puedes habilitar las alertas de fallos +para recibir notificaciones por correo electrónico cuando falla un job. + +![Alerta de fallo](/img/lightning_failure_alert.webp) + +La notificación por correo incluye los logs y un enlace al run fallido, para que +puedas inspeccionarlo y empezar a diagnosticar el problema. + +![Correo de fallo](/img/lightning_failure_email.webp) + +![Run fallido](/img/lightning_failed_run.webp) + +### Resúmenes por correo electrónico {#email-digests} + +También en `Project Settings > Collaboration` puedes elegir recibir resúmenes +diarios, semanales o mensuales de un proyecto por correo electrónico, con los +runs correctos y fallidos de cada uno de tus workflows. + +![Configuración del resumen por correo](/img/lightning_digest.webp) + +![Resumen por correo](/img/lightning_weekly_digest.webp) + +:::note + +Si quieres ajustar la configuración de tus notificaciones y eres colaborador en +más de 1 proyecto, tendrás que ir a la página `Project Settings > Collaboration` +de _cada_ proyecto al que perteneces. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/oauth.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/oauth.md new file mode 100644 index 000000000000..7c0a53e243fc --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/oauth.md @@ -0,0 +1,164 @@ +--- +title: Autenticación OAuth +sidebar_label: Autenticación OAuth +slug: /oauth +translation_source_hash: 1db156d8415788d53449388418ee834ee5a733c2 +translation_review_status: machine +--- + +Algunas aplicaciones exigen [OAuth](https://oauth.net/2/) como método de +autenticación para conectarse con aplicaciones de terceros y hacer solicitudes a +través de sus API. OpenFn te permite conectarte con aplicaciones mediante su +autenticación OAuth. Para usar esta funcionalidad en tus workflows de OpenFn, +tienes que configurar clientes y credenciales OAuth para tus instancias o +proyectos. Esta guía te explica cómo gestionar los clientes y las credenciales +OAuth. + +## Clientes OAuth {#oauth-clients} + +### ¿Qué es un cliente OAuth y cuándo lo necesito? {#what-is-an-oauth-client-and-when-do-i-need-it} + +Al configurar OAuth para una aplicación, autorizas a OpenFn a conectarse e +interactuar con esa aplicación dentro de un conjunto de permisos (scopes) que tú +defines. Por ejemplo, podrías configurar una autorización OAuth para que OpenFn +se conecte a tu cuenta de Google Sheets y lea y siga los cambios en tu nombre. +En este ejemplo, tienes que configurar un cliente de OpenFn que represente una +instancia de OpenFn en Google y que reúna todos los permisos que OpenFn necesita +en tu nombre. Todas las solicitudes y respuestas de la API pasan por el cliente +de OpenFn y se autorizan con un token de autorización que guarda el cliente. + +En la mayoría de los casos basta con configurar un cliente por aplicación, pero +según los requisitos del proyecto y las políticas de la organización, puede +haber varios clientes configurados para una misma aplicación. Estos clientes +pueden pertenecer al mismo usuario de OpenFn o a usuarios distintos, y pueden +estar disponibles para proyectos distintos. + +Por cada aplicación que necesites conectar a OpenFn, tienes que configurar al +menos un cliente para tus proyectos. + +Los clientes OAuth se pueden configurar en la +[página de credenciales del proyecto](/manage-projects/manage-credentials.md) o +en la [página de credenciales del usuario](/manage-users/user-credentials.md). + +### Crear un cliente OAuth {#creating-an-oauth-client} + +Si todavía no tienes un cliente OAuth configurado para tu proyecto, verás una +sección vacía con un botón que te invita a crear un cliente, como se muestra a +continuación. + +![Nuevo cliente](/img/create_new_oauth_client.webp) + +Si no, verás la lista de clientes OAuth a los que tienes acceso. Para crear un +cliente nuevo, haz clic en el botón `Add new` y selecciona +`OAuth client [Advanced]` en el menú desplegable. + +![Menú desplegable de OAuth](/img/oauth_dropdown.webp) + +:::tip + +Asegúrate de agregar https://app.openfn.org/authenticate/callback como URL de +callback de la aplicación cuando habilites la autenticación OAuth en la +aplicación de terceros. (Nota: si no usas app.openfn.org, reemplaza +`https://app.openfn.org/` por la URL base de _tu_ despliegue de OpenFn). + +Para ver indicaciones específicas de cada aplicación (por ejemplo, cómo +configurar un cliente OAuth [para Google Sheets](/adaptors/googlesheets)), +consulta la [documentación del adaptor](/adaptors) correspondiente. + +::: + +### Compartir clientes OAuth {#sharing-oauth-clients} + +Un superusuario puede compartir clientes OAuth con proyectos de dos formas: + +1. Hacer que un cliente sea global +2. Compartirlo con proyectos concretos + +Puede hacerlo en la ventana modal de configuración del cliente OAuth, al crear +el cliente o al editarlo. + +![Editar cliente OAuth](/img/oauth_client_edit.webp) + +### Hacer que los clientes OAuth sean globales {#making-oauth-clients-global} + +Cuando un cliente OAuth es global, los usuarios de la instancia pueden acceder a +él y crear credenciales a partir de él. + +Para que un cliente sea global, baja hasta la sección `Manage Project Access` de +la ventana modal de configuración del cliente OAuth, marca la casilla +`Make client global (allow any project in this instance to use this client)` y +guarda los cambios. Todos los proyectos de la instancia tendrán acceso al +cliente, y los usuarios con rol Owner, Admin o Editor en esos proyectos podrán +crear credenciales a partir de él. + +![Acceso de proyectos al cliente OAuth](/img/manage_project_access.webp) + +### Compartir clientes OAuth con proyectos {#sharing-oauth-clients-with-projects} + +Para compartir un cliente OAuth con proyectos concretos, baja hasta la sección +`Manage Project Access` de la ventana modal de configuración del cliente OAuth. +Abre el menú desplegable de proyectos, selecciona un proyecto y haz clic en el +botón para agregarlo y darle acceso al cliente. + +![Compartir cliente OAuth](/img/share_oauth_client.webp) + +## Credenciales OAuth {#oauth-credentials} + +### Crear una credencial a partir de un cliente OAuth {#creating-a-credential-from-an-oauth-client} + +Cada cliente necesita un token de autenticación para autenticar las solicitudes +que se hacen a la aplicación en nombre del usuario. En OpenFn, estos tokens se +crean como credenciales y se asocian a los clientes. + +1. Para crear una credencial a partir de un cliente OAuth, haz clic en el botón + "Add new" y selecciona `Credential` en el menú desplegable, o haz clic en el + botón `create a new credential`. + +![Nueva credencial](/img/oauth_dropdown.webp) + +![Crear nueva credencial](/img/create_new_cred.webp) + +2. Luego, en la ventana modal de tipo de credencial, busca y selecciona el + cliente OAuth que quieres usar para crear la credencial OAuth. Se abrirá una + nueva ventana modal para que configures la credencial con el nombre, los + permisos (scopes) necesarios y la versión de la API. +3. Cuando hayas completado el formulario, haz clic en el botón + `Sign in with [your OAuth Client name]` para autorizar el cliente OAuth. Este + botón abre una nueva pestaña para que le concedas a OpenFn un token de + autorización con el que autenticar tus solicitudes. + +:::note + +Después de iniciar sesión, tendrás que darle acceso a OpenFn haciendo clic en +`Allow` en la ventana modal de permisos. Ten en cuenta que esto puede verse +distinto según la aplicación, pero el objetivo es darle permiso a OpenFn para +realizar ciertas acciones en la aplicación en tu nombre. El usuario que +autentica los clientes OAuth debería tener los permisos necesarios en la +aplicación. + +::: + +### Eliminar clientes y credenciales {#deleting-clients-and-credentials} + +Para eliminar una credencial o un cliente, haz clic en `Delete`. + +![Editar cliente OAuth](/img/oauth_client_edit.webp) + +Aparece un mensaje para que confirmes la acción. + +En cuanto confirmes que quieres eliminar una credencial, recibirás un correo +electrónico que te avisa de que la credencial quedó programada para eliminarse. + +La fecha de eliminación depende de un período de gracia que configura el +administrador de tu instancia. En la +[instancia alojada de OpenFn](https://app.openfn.org/), la credencial se elimina +de forma permanente al cabo de 7 días. + +### Más sobre la gestión de credenciales {#more-on-managing-credentials} + +Consulta la documentación sobre +[cómo gestionar las credenciales de usuario](/manage-users/user-credentials.md) +para saber más sobre la gestión de credenciales de las aplicaciones que integras +con OpenFn. + +### Ejemplo de configuración de un cliente OAuth {#example-oauth-client-configuration} diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/platform-mgmt.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/platform-mgmt.md new file mode 100644 index 000000000000..ce68d5953313 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/platform-mgmt.md @@ -0,0 +1,96 @@ +--- +title: Gestión de proyectos +translation_source_hash: e35856d95724118bd7c59b0f7f385392009870ac +translation_review_status: machine +--- + +## ¿Qué es un proyecto? {#what-is-a-project} + +Un `Project` en OpenFn es un espacio de trabajo compartido de un equipo u +organización, que contiene sus workflows, credenciales y colaboradores, todos +limitados a ese proyecto. + +## Gestionar proyectos {#managing-projects} + +En la versión `v2.7.14` agregamos una tabla `Projects` para que los usuarios +puedan gestionar sus proyectos de OpenFn en forma de tabla. Es la página nueva +que verás cada vez que inicies sesión en tu cuenta de OpenFn. Cuando haces clic +en `Projects` en el menú lateral, ves la lista de proyectos a los que tienes +acceso como colaborador. + +![Tabla de proyectos](/img/projects-table.webp) + +## Crear un proyecto nuevo {#creating-a-new-project} + +Para crear un proyecto nuevo, sigue estos pasos: + +1. Inicia sesión en tu cuenta de OpenFn o, si estás en otro proyecto, ve a la + tabla de proyectos haciendo clic en `Projects` en la ruta de navegación. +2. En la tabla de proyectos, haz clic en `Create project`. Se abrirá una ventana + modal para que ingreses los datos del proyecto. +3. Escribe el `name` y la `description` del proyecto nuevo. +4. Si usas OpenFn en la nube, tendrás que seleccionar la `billing account` a la + que se facturará el proyecto nuevo. + +:::info Para usuarios en la nube en app.openfn.org + +1. Los proyectos de una misma cuenta de facturación deberían tener nombres + únicos. +2. Cada usuario tiene un proyecto inicial gratuito. Para crear un proyecto + nuevo, necesitas un método de pago válido y elegir un plan. + +::: + +![Ventana modal para crear un proyecto](/img/create-project-modal.webp) + +## Actualizar la información del proyecto {#updating-project-information} + +Puedes ver la información de tu proyecto en `Settings` (en el menú lateral de la +aplicación). Ahí puedes ver o editar el nombre y la descripción del proyecto. + +![Resumen del proyecto](/img/lightning_project_overview.webp) + +También puedes exportar todo tu proyecto "como código", ya sea para guardarlo o +para editarlo localmente. Encontrarás más información sobre esta funcionalidad +en nuestra [página de portabilidad](/deploy/portability.md). + +## Gestionar la concurrencia del proyecto {#managing-project-concurrency} + +OpenFn admite runs concurrentes de workflows y proyectos. Esto significa que +varios runs del mismo workflow o proyecto pueden ejecutarse al mismo tiempo, +siempre que estén configurados para ejecutarse en paralelo. + +Para gestionar la concurrencia del proyecto, usa la sección `Concurrency` de la +configuración del proyecto. + +![Concurrencia del proyecto](/img/configuring-project-concurrency.webp) + +Puedes habilitar o deshabilitar la ejecución en paralelo de un proyecto. Cuando +la ejecución en paralelo está deshabilitada, solo puede ejecutarse un run a la +vez de un workflow del proyecto. + +### Workflows en modo síncrono {#sync-mode-workflows} + +Ten en cuenta que los workflows que se disparan con un webhook y están +configurados para responder de forma síncrona suelen ejecutarse en una cola de +prioridad (para reducir los tiempos de solicitud y respuesta HTTP), según lo +configure el superusuario de tu instancia, e IGNORAN todos los límites de +concurrencia. Es decir, usan el número máximo de workers de prioridad +disponibles para reducir los tiempos de respuesta. + +:::warning Los workflows en modo síncrono ignoran la concurrencia del proyecto + +Usan el número máximo de workers disponibles, según lo configure el +administrador de tu instancia. + +::: + +### Limitador de concurrencia por workflow {#workflow-level-concurrency-limiter} + +:::info Concurrencia por proyecto frente a concurrencia por workflow + +La concurrencia por proyecto tiene prioridad sobre la concurrencia por workflow. +Esto significa que, si la ejecución en paralelo está deshabilitada en un +proyecto, se ignora la configuración de concurrencia de sus workflows. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/retention-periods.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/retention-periods.md new file mode 100644 index 000000000000..3173e5d2b4b2 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/retention-periods.md @@ -0,0 +1,35 @@ +--- +title: Retención del historial +translation_source_hash: d1dcb2afd88e4d5f16731dcfe028463b30448485 +translation_review_status: machine +--- + +En la sección "Data Storage" puedes configurar durante cuánto tiempo quieres que +se guarde el historial de runs de un proyecto. + +:::tip + +Ten en cuenta que, si usas Lightning en OpenFn.org, el plan de un proyecto puede +limitar durante cuánto tiempo guardamos tu historial. Siempre puedes guardarlo +menos tiempo, y los planes de pago ofrecen un almacenamiento más largo. + +::: + +### ¿Qué es el historial? {#what-is-history} + +El historial incluye las work orders, los runs, los logs y los dataclips de +entrada y salida asociados. + +### ¿Por qué querría reducir el período de retención del historial de un proyecto? {#why-would-i-want-to-reduce-my-history-retention-period-for-a-project} + +Algunos administradores de proyectos deciden guardar el historial durante menos +tiempo (un mes o 90 días) para reducir los costos de almacenamiento de datos o +limitar su huella de datos. + +### ¿Qué pasa cuando se eliminan work orders, runs, logs o dataclips antiguos? {#what-happens-when-old-work-orders-runs-logs-or-dataclips-get-removed} + +Todo el historial se elimina de OpenFn y ya no se puede consultar en la +plataforma. Si antes solicitaste +[exportaciones del historial de work orders](/manage-projects/io-data-storage.md#export-history), +podrás seguir accediendo a los CSV exportados desde la página de exportaciones +del historial, en la configuración del proyecto. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/staging-prod.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/staging-prod.md new file mode 100644 index 000000000000..7e99a6c175a7 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/staging-prod.md @@ -0,0 +1,126 @@ +--- +title: Control de versiones para proyectos de staging y producción +sidebar_label: Proyectos de staging y producción +slug: /staging-prod +translation_source_hash: 87d54b2bb53b4c41a1fae83e0cb1cf5514e4d665 +translation_review_status: machine +--- + +Usar proyectos separados de producción y de staging (pruebas) para construir y +probar tus workflows antes de empezar a usarlos en producción es una práctica +segura y eficiente. El [control de versiones](/manage-projects/link-to-gh.md) +hace que este proceso sea fluido. Esta guía te explica cómo configurar tus +proyectos de OpenFn y tu repositorio de GitHub, y te da dos ejemplos de cómo +gestionar tu flujo `Staging > Production`: uno para proyectos nuevos y otro para +proyectos existentes en los que quieres agregar un proyecto y una rama de +staging. + +### Configuración para proyectos nuevos {#setup-for-new-projects} + +1. Primero, crea un proyecto `Production` y otro `Staging` en OpenFn (2 + proyectos) + +![Proyectos de producción y staging](/img/openfn_prod_staging.webp) + +2. Elige o crea un repositorio de GitHub para tu proyecto, y crea una rama + `staging` + +![Ramas main y staging](/img/staging_prod_branches_gh.webp) + +3. Conecta tus proyectos a las ramas `main` y `staging`, respectivamente. Sigue + [esta guía](/manage-projects/link-to-gh.md) para configurar la conexión +4. En cada repositorio, crea un archivo `.js` vacío para tu job. Asegúrate de + que tengan el mismo nombre y la misma ruta en cada repositorio (por ejemplo, + `upsert-contacts.js`). Estos archivos guardarán el código del job al que se + vincularán en el siguiente paso. + +5. Cuando conectaste las ramas a tus proyectos en el paso 3, se creó + automáticamente un archivo `spec.yaml` en la rama después de la primera + sincronización (junto con otros dos archivos de configuración). Abre estos + archivos en GitHub y busca tu job en el archivo. Reemplaza el contenido de + `body` por `path: {path to the related js file}`. Haz esto tanto en la rama + `main` como en la rama `staging`. + +![Spec de main](/img/path_main.webp) ![Spec de staging](/img/path_staging.webp) + +6. ¡Ya está todo configurado! +7. Para sincronizar un cambio de tu proyecto de staging a producción **con la + aplicación de OpenFn**, ve a tu proyecto `Staging` en OpenFn y edita tu job. + Luego ve a `Settings` > Sync to `GitHub` del proyecto y haz clic en + `Initiate Sync to Branch`. +8. También puedes editar directamente el código del job en GitHub y hacer commit + de los cambios en la rama `staging` en GitHub. +9. Cuando hayas hecho commit de los cambios en tu rama `staging`, verás en + GitHub un aviso de que hubo cambios recientes. Haz clic en + `Compare & pull request`. + +![Crear pull request](/img/staging_pushes.webp) + +10. Crea una pull request. Incluirá automáticamente todos los cambios que se + hicieron en los archivos de la rama staging. + +![Guardar pull request](/img/create_pr.webp) + +11. Según el flujo de trabajo de GitHub de tu equipo, pide a alguien que apruebe + y haga merge de la pull request, o haz clic en `Merge pull request`. + +12. Tus cambios se desplegarán automáticamente en tu proyecto `Production` de + OpenFn (vinculado a la rama `main` de GitHub). + +### Configuración para proyectos existentes {#setup-for-existing-projects} + +1. Primero, asegúrate de que el código de todos tus jobs esté guardado en + archivos `.js` separados (como `Notify-CHW-upload-successful.js`) en GitHub, + vinculados en tu `spec.yaml` de esta forma: + +```yaml + +Notify-CHW-upload-successful: + name: Notify-CHW-upload-successful + adaptor: '@openfn/language-http@latest' + enabled: true + # credential: + # globals: + body: | + path: ./workflow/Notify-CHW-upload-successful.js + +``` + +Encontrarás más información sobre esta configuración en nuestra +[documentación de GitHub](/manage-projects/link-to-gh.md#sync-from-github-to-openfn). + +2. Con esto listo, crea una nueva rama `staging` en GitHub a partir de tu rama + de producción `main`, que guarda tu proyecto actual. Para hacerlo, en tu + repositorio de GitHub, entra en `Branches` (donde dice `1 Branch` en la + captura de pantalla de abajo). + +![Ramas](/img/1_branch.webp) + +3. Haz clic en `New branch`, ponle un nombre como `staging` y, si ya tienes + varias ramas, asegúrate de que el origen sea `main`. Luego haz clic en + `Create new branch`. + +![Nueva rama](/img/new_branch.webp) + +4. Ve a tu nueva rama `staging`. **Este paso es importante. Fíjate en que la + nueva rama contiene ahora los 3 archivos de configuración (`config.json`, + `spec.yaml` y `state.json`) que estaban en la rama main. Elimínalos de la + rama `staging`.** En los pasos siguientes se crearán otros nuevos, propios de + la rama staging. + +5. Ahora ve a OpenFn y crea un nuevo proyecto `Staging`. + +6. Sigue [esta guía](/manage-projects/link-to-gh.md) para configurar la conexión + de GitHub con tu rama `staging`, y haz clic en `Initiate a sync` (desde la + página `Settings > Sync to GitHub` del proyecto). Esto creará los archivos de + configuración necesarios en la rama de GitHub. + +7. En el archivo `spec.yaml` recién generado en la rama `staging` de GitHub, + vincula los archivos `.js` de tus jobs como se explica en el paso 1. + +8. Cuando inicies una nueva sincronización desde OpenFn, el código de los jobs + de los workflows configurados en la aplicación se sincronizará con los + archivos de job de OpenFn correspondientes en GitHub. + +9. Para hacer cambios futuros en tu proyecto "Staging", sigue los pasos 7 a 12 + de la sección `Setup for new projects` de esta guía. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/user-roles-permissions.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/user-roles-permissions.md new file mode 100644 index 000000000000..c1c002e1b7be --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/user-roles-permissions.md @@ -0,0 +1,59 @@ +--- +title: Roles y permisos de usuario +sidebar_label: Roles de usuario +translation_source_hash: ac3a8fdca96392acaf666020cf7c03ebdf796adf +translation_review_status: machine +--- + +Cuando invitas a usuarios de OpenFn a trabajar en tu proyecto como +`Collaborators`, se les asigna un `Role` que determina sus permisos. Los cuatro +roles disponibles son: Owner (**solo 1 por proyecto**), Admin, Editor y Viewer. +Consulta la tabla de abajo para ver los permisos de cada rol. + +| Contexto | Acción | Owner | Admin | Editor | Viewer | +| :-------- | :--------------------------------------------------------------------------- | :----------------- | :----------------- | :----------------- | :----------------- | +| Workflows | Crear un workflow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Workflows | Editar un job de un workflow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Workflows | Agregar o quitar un método de autenticación de webhook de un workflow | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Workflows | Eliminar un workflow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Workflows | Ejecutar desde el Inspector | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Workflows | Seleccionar las 5 entradas más recientes de un job de un workflow | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| History | Ver, buscar y filtrar en la página History | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| History | Ver un run desde el historial de work orders | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| History | Ver una entrada desde el historial de work orders | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| History | Ejecutar desde el historial de work orders | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: | +| Settings | Ver el nombre del proyecto | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Settings | Editar el nombre del proyecto | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Ver la descripción del proyecto | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Settings | Editar la descripción del proyecto | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Exportar el proyecto | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Settings | Eliminar un proyecto | :heavy_check_mark: | :x: | :x: | :x: | +| Settings | Ver las credenciales del proyecto, su tipo y su propietario | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Settings | Agregar o quitar un método de autenticación de webhook del proyecto | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Cambiar el requisito de MFA del proyecto | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Agregar o quitar colaboradores del proyecto | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Ver los colaboradores del proyecto (project_users, rol, resúmenes y alertas) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Settings | Editar sus propios resúmenes y alertas | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Settings | Editar los resúmenes y alertas de otros | :x: | :x: | :x: | :x: | +| Settings | Cambiar la política de almacenamiento de dataclips de entrada y salida | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Cambiar el período de retención del historial | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Actualizar la conexión del proyecto o repositorio de GitHub | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Settings | Iniciar la sincronización con GitHub | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | + +### Privilegios de superusuario {#super-user-privileges} + +Cada instancia de OpenFn tiene un usuario con el rol de superusuario, que le da +control administrativo total de la plataforma. Esto incluye la gestión de +usuarios, proyectos, el registro de auditoría y la autenticación de terceros, +con los siguientes privilegios de superusuario: + +| Aspecto | Descripción | Funcionalidades y permisos | +| --------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| Gestión de usuarios | La gestión de los usuarios de una instancia de OpenFn | Crear, editar y eliminar usuarios | +| Gestión de proyectos | Cómo se crean y gestionan los proyectos en la instancia | Crear, eliminar y editar un proyecto, y asignarle usuarios | +| Autenticación | Gestión del acceso de terceros para los usuarios de la instancia | Configurar OpenID Auth para la instancia | +| Registro de auditoría | Auditabilidad y gestión de cambios | Ver el historial de las acciones relevantes de los usuarios en la instancia para auditorías | + +Si usas la plataforma de OpenFn alojada (por ejemplo, app.openfn.org), escribe a +[support@openfn.org](mailto:support@openfn.org) si necesitas contactar al +superusuario para solicitar proyectos nuevos o cambios de configuración. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/webhook-auth.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/webhook-auth.md new file mode 100644 index 000000000000..9d04ca7f5200 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/webhook-auth.md @@ -0,0 +1,77 @@ +--- +title: Seguridad de webhooks +sidebar_label: Seguridad de webhooks +slug: /webhook-security +translation_source_hash: dc4bb5877d05c57f51762f307fbc80288cb9db96 +translation_review_status: machine +--- + +Esta página te guía por los pasos para agregar una capa extra de seguridad a tu +webhook. + +## Agregar un método de autenticación de webhook {#adding-a-webhook-authentication-method} + +En tus proyectos de OpenFn puedes usar webhooks para recibir datos de +aplicaciones externas con un [trigger webhook](/build/triggers.md). Para más +seguridad, cuando usas un webhook puedes exigir que las aplicaciones externas se +autentiquen antes de enviar datos a tu proyecto. + +OpenFn admite la autenticación HTTP básica con nombre de usuario y contraseña, y +la autenticación con clave de API mediante el encabezado de solicitud +`x-api-key`. + +### Agregar autenticación desde `Project Settings` {#adding-authentication-via-project-settings} + +Puedes agregar un método de autenticación nuevo en `Webhook Security`, dentro de +los `Project Settings`. La autenticación que configures aquí se puede usar luego +en cualquiera de los workflows de este proyecto. + +![Webhook Security en Project Settings](/img/lightning_auth_project_settings.webp) + +Después de hacer clic en `New auth method`, elige el tipo: Basic HTTP o API Key +Authentication. + +![Método de autenticación nuevo](/img/lightning_choose_auth_method.webp) + +#### Basic Auth + +Para Basic Auth, ponle un nombre, elige un nombre de usuario y una contraseña, y +haz clic en `Create Auth Method`. + +![Basic Auth](/img/lightning_basic_auth.webp) + +#### API Key + +Para API Key, solo elige un nombre y haz clic en `Create Auth Method`. Se genera +una clave de API para ti. + +![Autenticación con API Key](/img/lightning_api_auth.webp) + +En esta página también puedes editar o eliminar tus métodos de autenticación. + +// screenshot + +Cuando agregas un método de autenticación a un webhook, aparece en +`Linked Triggers`. + +![Linked Triggers](/img/lightning_linked_triggers.webp) + +![Linked Triggers](/img/lightning_linked_triggers2.webp) + +### Agregar autenticación desde un workflow {#adding-authentication-via-a-workflow} + +En tus workflows puedes usar los métodos de autenticación que creaste en +`Project Settings`, o crear uno nuevo. + +Cuando hagas clic en `Add authentication`, en `Webhook Authentication`, +selecciona uno o varios métodos existentes, o haz clic en +`Create a new webhook auth method`. Consulta las secciones `Basic Auth` y +`API Key` de arriba para ver cómo agregarlos. + +Cuando agregas un método de autenticación, aparece en la configuración del +trigger webhook. + +![Linked Triggers](/img/lightning_workflow_trigger_added.webp) + +Solo las solicitudes que usen estos datos de autenticación obligatorios podrán +enviar datos a tu workflow. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/workflow-dashboard.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/workflow-dashboard.md new file mode 100644 index 000000000000..4afb55e2cb5d --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-projects/workflow-dashboard.md @@ -0,0 +1,27 @@ +--- +title: Monitoreo de workflows +sidebar_label: Monitoreo de workflows +slug: /monitoring-workflows +translation_source_hash: c45b9bb5f99bb94d614245195adb31c0b8b3bb3b +translation_review_status: machine +--- + +En este artículo aprenderás a monitorear el estado de tus workflows. + +## Panel de workflows {#workflow-dashboard} + +En la página principal de workflows puedes ver un resumen de la cantidad de work +orders y runs de tu proyecto, y de la cantidad y la proporción de éxitos y +fallos. + +![Panel de workflows](/img/lightning_workflow_dashboard.webp) + +Para investigar más a fondo, haz clic en la cantidad de work orders de un +workflow, como se muestra, y llegarás al History de ese workflow. Por ejemplo, +estas son las 7 work orders en estado fallido: + +![Work orders fallidas](/img/lightning_failed_work_orders.webp) + +Consulta nuestra +[documentación de History](/monitor-history/activity-history.md) para saber más +sobre cómo gestionar y monitorear runs y work orders. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-users/api-tokens.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-users/api-tokens.md new file mode 100644 index 000000000000..3242063e62a6 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-users/api-tokens.md @@ -0,0 +1,36 @@ +--- +title: Crear y administrar tokens de API +sidebar_label: Tokens de API +slug: /api-tokens +translation_source_hash: c69fe92dda6de7120298b55845a5d47a6e3103eb +translation_review_status: machine +--- + +Este artículo te explica cómo crear un token de API de acceso personal. + +### Acerca de los tokens de API {#about-api-tokens} + +OpenFn da permisos de API a los usuarios para que puedan crear sus proyectos en +la plataforma, o interactuar con ellos, a través de la API. Para acceder a la +plataforma a través de la API necesitas un token de acceso personal (Personal +Access Token). Para saber más sobre cómo crear o actualizar tu proyecto mediante +código, visita nuestra página [Portabilidad](/deploy/portability.md). + +Tu acceso a la API te da el mismo nivel de permisos que tienes como usuario en +OpenFn (por ejemplo, si tu perfil tiene acceso de nivel Admin, tu usuario de la +API también tendrá permisos de Admin). + +### Crear un token de API {#creating-an-api-token} + +Puedes administrar tus tokens en tu perfil de usuario. + +![API Tokens Profile](/img/lightning_user_profile_api_tokens.webp) + +![API Tokens](/img/lightning_no_api_token.webp) + +1. Haz clic en `Generate New Token` para crear uno nuevo. + +![New Token](/img/lightning_new_api_token.webp) + +2. Asegúrate de copiar tu token nuevo de inmediato. Después no podrás verlo ni + copiarlo. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-credentials.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-credentials.md new file mode 100644 index 000000000000..e04e75b2b7f0 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-credentials.md @@ -0,0 +1,55 @@ +--- +title: Administrar las credenciales de usuario +sidebar_label: Credenciales de usuario +slug: /user-credentials +translation_source_hash: 63ee09db3568f7693da2dbc1741446c246cb6182 +translation_review_status: machine +--- + +Puedes administrar todas las credenciales de las que eres propietario en la +página `Credentials` de tu perfil. En este artículo te explicamos cómo +administrar y compartir entre proyectos las credenciales que te pertenecen. + +### Todas tus credenciales en un solo lugar {#all-your-credentials-in-one-place} + +La página `Credentials` de tu `User Settings` te permite agregar, ver, editar o +eliminar las credenciales que te pertenecen. Es el lugar central para +administrar tus credenciales en todos los proyectos en los que colaboras. + +![User Credential](/img/lightning_user_profile_credentials.webp) + +![User Credentials List](/img/lightning_edit_user_credential.webp) + +Para saber cómo configurar una credencial nueva, consulta la página +[Administrar credenciales](/manage-projects/manage-credentials.md). + +Puedes actualizar el nombre y los datos de inicio de sesión de una credencial +después de hacer clic en `Edit`. + +![User Credential Edit View](/img/lightning_cred_edit_view.webp) + +### Compartir credenciales {#share-credentials} + +También puedes dar acceso a varios proyectos a una credencial que te pertenece. + +Para agregar o quitar el acceso de un proyecto, haz clic en `Edit` en la +credencial que quieres compartir y elige el proyecto en el menú desplegable de +`Project Access`. + +![Update Project Access](/img/lightning_share_cred_with_project.webp) + +:::info Las credenciales compartidas siguen siendo secretas + +Si compartes una credencial con un proyecto, los colaboradores de ese proyecto +pueden _usar_ la credencial en sus workflows, pero no pueden ver los datos de +inicio de sesión que contiene. + +::: + +:::tip + +Si quieres compartir los datos de inicio de sesión de la credencial, usa un +método seguro, como un administrador de contraseñas o una plataforma de +mensajería cifrada. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-profile.md b/i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-profile.md new file mode 100644 index 000000000000..ede3416e746a --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/manage-users/user-profile.md @@ -0,0 +1,69 @@ +--- +title: Actualizar el perfil de usuario +sidebar_label: Perfil de usuario +slug: /user-profile +translation_source_hash: 599daf8fdede00b97cdb29723b9c6c392d68afea +translation_review_status: machine +--- + +Este artículo te explica cómo ver y actualizar tu información de usuario en tu +perfil de usuario. + +![User Profile](/img/lightning_select_user_profile.webp) + +### Cambiar el correo electrónico y la contraseña {#change-email-and-password} + +Puedes cambiar la dirección de correo electrónico asociada a tu perfil y +actualizar tu contraseña. + +![Change Email Password](/img/lightning_change_email_pw.webp) + +### Habilitar la autenticación multifactor {#enable-multi-factor-authentication} + +Al habilitar la autenticación multifactor, agregas una capa extra de seguridad a +tu cuenta, porque para iniciar sesión se necesita algo más que una contraseña. + +![Enable MFA](/img/lightning_enable_MFA.webp) + +Puedes vincular tu cuenta a una app de autenticación o a una extensión del +navegador, como 1Password o Authy. Una vez configurada, la app genera una +contraseña de un solo uso que tienes que ingresar al iniciar sesión para +verificar tu identidad cada vez. + +Para configurar la autenticación multifactor, usa una app de autenticación o una +extensión del navegador para escanear el código QR que aparece en tu perfil. + +También puedes configurarla ingresando en la app la clave secreta que se genera +en tu perfil. + +### Eliminar la cuenta {#account-deletion} + +En tu perfil de usuario también puedes eliminar tu cuenta de OpenFn. + +![Delete Account](/img/lightning_delete_account_cropped.webp) + +Para eliminar tu cuenta, haz clic en el botón **"Delete my account"**. Se te +pedirá que confirmes la eliminación ingresando tu dirección de correo +electrónico y haciendo clic en **"Delete Account"**. + +Cuando confirmas que quieres eliminar tu cuenta, se programa su eliminación +según el período de gracia que haya definido el administrador de tu instancia. + +:::info + +El período de gracia es el tiempo que tienes para cambiar de opinión y cancelar +la eliminación antes de que tu cuenta se elimine definitivamente. El valor +predeterminado es de 7 días. + +::: + +#### Eliminación de la cuenta y auditoría {#account-deletion-and-auditing} + +Ten en cuenta que, si usaste tu cuenta para crear work orders o runs +manualmente, no se eliminará de forma permanente de la instancia hasta que se +elimine esa actividad relacionada. En esos casos, formas parte del registro de +auditoría de un proyecto, y es posible que el administrador de la instancia no +pueda eliminar tu cuenta de forma permanente. + +Si usas https://app.openfn.org, tienes que cancelar cualquier suscripción activa +antes de poder eliminar tu cuenta. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/activity-history.md b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/activity-history.md new file mode 100644 index 000000000000..6d88aee93e1b --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/activity-history.md @@ -0,0 +1,93 @@ +--- +title: History y búsqueda en OpenFn +sidebar_label: History y búsqueda +translation_source_hash: 598667faa8236bba7833a136f1086d7a837ae6d7 +translation_review_status: machine +--- + +Para los administradores de la plataforma, `History` es la consola central para +supervisar toda la actividad de tus workflows activos. Sigue leyendo para +conocer sus componentes principales. + +## History + +La página `History` muestra una lista de todas las +[work orders](/get-started/terminology.md#work-order) y los +[runs](/get-started/terminology.md#run) que se procesaron en un proyecto. + +![History](/img/case-referral-history.webp) + +## Ejecución de workflows: work orders y runs {#workflow-execution-work-orders-and-runs} + +Los workflows de OpenFn se ejecutan así: + +1. Un `Trigger` del workflow se activa con un evento de webhook, un temporizador + cron o una acción manual. +2. Esto crea una `Work Order`: una solicitud para ejecutar un workflow con una + entrada determinada (por ejemplo, el envío de un formulario nuevo o un + registro de paciente que hay que procesar). Para que una `Work Order` se + complete, debería llegar a un step final con éxito (sin errores); así se + garantiza que el procesamiento terminó. +3. Después se ejecuta un `Run` para intentar completar el workflow con éxito. + Este run tendrá un [código de estado](/monitor-history/status-codes.md) que + indica si los steps del workflow se procesaron correctamente. +4. Si el primer `Run` falla, puedes volver a ejecutarlo para "reintentar" el + workflow. Se creará un segundo `Run`. Si tiene éxito, tanto el run como la + work order relacionada se actualizarán con el estado `success`. + +También puedes **cancelar** runs pendientes o **reintentar** work orders +completadas directamente desde la página History. Consulta +[Reintentar y cancelar runs](/monitor-history/rerunning-workflow.md) para más +detalles. + +![History Page](/img/history-page-annotated.webp) + +Consulta las demás páginas de esta sección para saber más sobre cómo +inspeccionar runs, solucionar problemas y volver a ejecutar runs fallidos. + +## Cómo funciona la búsqueda {#how-search-works} + +Con la barra de búsqueda de la página History puedes encontrar work orders cuyos +dataclips de entrada o salida _relacionados_, o cuyos logs de runs, contienen +cadenas de texto específicas. De forma predeterminada, el sistema busca solo en +los logs de runs, pero puedes elegir buscar en cualquiera de estas tres +opciones, o en todas: + +![Search Options](/img/search-options.webp) + +1. UUIDs de OpenFn de work orders, runs o steps +2. Cuerpos de los dataclips de entrada y salida +3. Logs de runs + +Si buscas texto dentro de un dataclip de entrada o salida o de los logs de runs, +se aplica una búsqueda `tsvector`. Este método de búsqueda te permite encontrar +work orders rápidamente y admite coincidencias parciales en todo el texto de los +logs de runs y en las "keys" y los "values" de tus dataclips. + +:::caution Es posible que los dataclips de entrada muy grandes o complejos no se +indexen + +Actualmente no es posible crear índices `tsvector` de más de 1 MB, por lo que es +posible que los dataclips de entrada muy grandes o complejos no aparezcan en los +resultados de búsqueda. Por lo general esto no ocurre hasta que te acercas a los +10 MB de JSON, pero la cantidad de lexemas y posiciones distintos de tu JSON +influye en el tamaño final del índice. + +Más información en la página +["text search limitations"](https://www.postgresql.org/docs/current/textsearch-limitations.html) +de la documentación de Postgres. + +::: + +Las coincidencias parciales funcionan mejor al principio de las palabras, así +que si buscas elementos que coincidan con `"newPatient"`, es mejor buscar +`"newPat"` que `"tient"`. (Si tienes dudas, las palabras completas o los IDs dan +los mejores resultados). + +## Buscar y filtrar resultados {#search--filter-results} + +Aunque puedes buscar cadenas de texto que aparecen en logs de runs o dataclips +concretos, es importante recordar que los resultados que se devuelven siguen +siendo **work orders**. Si los dataclips de salida del tercer step del primer +run de la work order "123" coinciden con tu búsqueda, verás la work order "123" +en los resultados. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/inspect-runs.md b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/inspect-runs.md new file mode 100644 index 000000000000..08e4607fd674 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/inspect-runs.md @@ -0,0 +1,31 @@ +--- +title: Inspeccionar runs y buscar desde la página History +sidebar_label: Inspeccionar runs +translation_source_hash: ca04c62187b3ec930749e2f0691da095925d7ac6 +translation_review_status: machine +--- + +Cada vez que OpenFn intenta ejecutar un workflow para una work order +determinada, se crea un [run](/get-started/terminology.md#run). Puedes ver, +filtrar y buscar todos los runs desde la página `History`. + +En pocas palabras, los runs nos dicen "qué pasó" cuando OpenFn intentó ejecutar +el workflow. Los runs tienen hora de inicio, hora de fin, logs y +[códigos de estado](/monitor-history/status-codes.md) que indican cuándo +ocurrieron, qué hicieron y si tuvieron éxito o no. + +## Inspeccionar runs {#inspect-runs} + +Mira el siguiente video tutorial +([o abre el enlace](https://youtu.be/xPgVZmJMT3w?si=bMf9wof_Qla-0ihW)) para ver +paso a paso cómo inspeccionar runs desde la página History. + + + +## Buscar en el historial y en los runs {#search-history-and-runs} + +Para aprender a buscar y filtrar el historial de work orders y runs desde la +página History, mira el siguiente video tutorial +([o abre el enlace](https://youtu.be/XIUykmLCxwQ?si=pCzefw4zyLxG1voE)). + + diff --git a/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/rerunning-workflow.md b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/rerunning-workflow.md new file mode 100644 index 000000000000..8b7e841d0680 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/rerunning-workflow.md @@ -0,0 +1,110 @@ +--- +title: Reintentar y cancelar runs +sidebar_label: Reintentar y cancelar +translation_source_hash: c00e54f15cb364cb77cc6975b6465eb09f520470 +translation_review_status: machine +--- + +Desde la página `History` puedes realizar acciones sobre work orders y runs +según su estado actual. Usa **Retry** para volver a ejecutar work orders +completadas, o **Cancel** para quitar runs pendientes de la cola. + +## Acciones disponibles según el estado de la work order {#available-actions-by-work-order-state} + +| Estado de la work order | Acciones disponibles | +| :------------------------------------------------------------------------------------------- | :------------------- | +| **Pending** (runs en espera en la cola) | Cancel | +| **Running** | Ninguna | +| **Estados finales** (Success, Failed, Crashed, Killed, Exception, Lost, Cancelled, Rejected) | Retry, Retry from | + +:::info Seleccionar work orders con estados distintos + +Si seleccionas varias work orders de categorías de estado distintas (por +ejemplo, algunas pendientes y otras fallidas), los botones Retry y Cancel se +deshabilitan. Para usar las acciones en bloque, selecciona solo work orders de +la misma categoría de estado. + +::: + +## Reintentar una work order {#retry-a-work-order} + +¿Falló un step de tu workflow? ¿Quieres volver a sincronizar datos históricos? +Sea cual sea el motivo, mira el siguiente video tutorial +([o abre el enlace](https://youtu.be/DvLRA6kloNE?si=U0NMx-HsCMZxeJwg)) para +aprender a volver a ejecutar tu workflow. + + + +### Reintentar desde la página History {#retry-via-history-page} + +Para volver a ejecutar tu workflow desde la página `History`: + +1. Busca tu `Work Order` fallida (usa la barra de búsqueda o los filtros si hace + falta) +2. Contrae la work order para ver los `Runs` relacionados +3. Haz clic en `rerun` junto al step desde el que quieres volver a ejecutar el + workflow. Elige el primer step para empezar desde el principio, o un step + posterior para volver a ejecutar el workflow a partir de ese step. +4. Esto crea un nuevo `Run` asociado a la misma work order. Revisa el `Status` + para ver si este run completó la work order con éxito. + +### Reintentar desde la vista del Inspector {#retry-via-inspector-view} + +Para volver a ejecutar tu workflow desde la página `Inspector`: + +1. Busca tu `Work Order` fallida (usa la barra de búsqueda o los filtros si hace + falta) +2. Contrae la work order para ver los `Runs` relacionados +3. Haz clic en `inspect` junto al step que quieres abrir en la vista `Inspector` + para seguir investigando el problema. +4. Se abre la vista `Inspector`, donde puedes ver el `Input` y el `Output` del + step fallido. Si hace falta, puedes editar la lógica personalizada en el + panel `Editor`. +5. Cuando quieras reintentar el workflow con el mismo Input, haz clic en + `Rerun from here`. Esto crea un nuevo `Run` para la misma work order. Ve a la + página `History` y revisa el `Status` para ver si este run completó la work + order con éxito. +6. Si prefieres crear una work order _nueva_ (en lugar de reintentar la misma), + puedes hacer clic en el menú desplegable junto a "Rerun from here" y elegir + _en su lugar_ `Create New Work Order`. + +## Cancelar runs pendientes {#cancel-pending-runs} + +Si hay runs atascados en la cola o se crearon por error, puedes cancelarlos. Al +cancelarlos, los runs pasan de `available` a `cancelled` y el estado de la work +order correspondiente pasa de `pending` a `cancelled`. Consulta +[Códigos de estado](/monitor-history/status-codes.md) para saber qué significa +cada estado. + +Hay varias formas de cancelar: + +- **Cancelar todos los runs de una work order:** haz clic en el botón de acción + de una fila de work order pendiente en la página History para cancelar todos + sus runs pendientes. +- **Cancelar un solo run:** haz clic en el botón de cancelar junto a un run, ya + sea en la lista de runs o en la página de detalle del run. + +:::note Runs que empiezan antes de la confirmación + +Si un run pendiente empieza a ejecutarse entre el momento en que abres el +diálogo de confirmación de la cancelación y el momento en que confirmas, ese run +**no** se cancela. Solo se ven afectados los runs que siguen en la cola en el +momento de la confirmación. + +::: + +## Acciones en bloque {#bulk-actions} + +Puedes actuar sobre varias work orders a la vez seleccionándolas con las +casillas de verificación de la página History: + +- **Cancelar en bloque:** selecciona work orders en estado `Pending` y haz clic + en el botón `Cancel` para cancelar todos los runs pendientes de las work + orders seleccionadas. +- **Reintentar en bloque:** selecciona work orders en un estado final (por + ejemplo, Failed o Crashed) y haz clic en el botón `Retry`. + +Los botones de acciones en bloque solo se habilitan cuando todas las work orders +seleccionadas pertenecen a la misma categoría de estado. Si seleccionas work +orders con estados distintos (por ejemplo, algunas pendientes y otras fallidas) +o solo work orders en ejecución, se deshabilitan todos los botones de acción. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/status-codes.md b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/status-codes.md new file mode 100644 index 000000000000..c78d0d1d6667 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/status-codes.md @@ -0,0 +1,58 @@ +--- +title: Códigos de estado de work orders y runs +sidebar_label: Códigos de estado +translation_source_hash: a81cb1fcb1e0bdd937dda745cf4db31bc9366f13 +translation_review_status: machine +--- + +## Estado de la work order {#work-order-status} + +Una `Work Order` es una solicitud para iniciar la ejecución de un workflow de +OpenFn con una entrada determinada (por ejemplo, "completar el workflow de +derivación del paciente 123"). Para una organización, la work order suele ser la +unidad de valor de negocio, porque los usuarios quieren asegurarse de que cada +solicitud de workflow se procesó con éxito. + +Como los administradores pueden querer ejecutar la misma work order varias veces +(por ejemplo, "intentar completar de nuevo el workflow de derivación del +paciente 123 ahora que el sistema de gestión de casos del gobierno volvió a +estar en línea"), el "estado" de una work order se determina por el estado del +_último_ run de esa work order. + +Es decir, si la work order "completar el workflow de derivación del paciente +123" se ejecutó dos veces y el primer run falló pero el segundo tuvo éxito, el +"estado" de esa work order será "success". + +## Estado del run {#run-status} + +Cada run tiene un estado que indica si se completó con éxito. + +| Estado | Indicador | Tipo | ¿Aborta el run?\* | Descripción o ejemplo | +| :-------- | :-------: | :----------------: | :---------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Pending | ⚪ | | - | El run está esperando a que haya un worker disponible para empezar a ejecutarse | +| Running | 🔵 | | - | El run sigue en curso | +| Success | 🟢 | | - | O todos los steps de este run tuvieron éxito _o_ cada error se manejó correctamente. Técnicamente, un run tiene éxito si el step final de cada rama (el nodo hoja) tiene éxito | +| Failed | 🔴 | JobError | No | Una solicitud falló con el código de estado 404 | +| Failed | 🔴 | TypeError | No | Intentar hacer referencia a `state.data.patient.age` cuando `state.data.patient` es `undefined` | +| Failed | 🔴 | RangeError | No | Llamar a `state.patients[5]` cuando solo existen 2 pacientes | +| Crashed | 🟠 | SyntaxError | Sí | Tienes JavaScript con errores y el worker no puede compilar el código de tu job | +| Crashed | 🟠 | ReferenceError | Sí | Tienes una variable sin declarar en el código de tu job | +| Cancelled | ⚪ | | Sí | El run estaba en la cola, pero se [quitó manualmente](/monitor-history/rerunning-workflow.md#cancel-pending-runs) | +| Killed | 🟡 | SecurityError | Sí | Tu código no pasó los controles de seguridad, por ejemplo, intentó usar `eval` | +| Killed | 🟡 | ImportError | Sí | Intentaste importar un módulo externo que no permitimos | +| Killed | 🟡 | OomError | Sí | Tu run usó más memoria de la que permite la instancia de Lightning | +| Killed | 🟡 | StateTooLargeError | Sí | Tu step devolvió un objeto `state` que superó el 25 % del límite total de memoria del run | +| Killed | 🟡 | TimeoutError | Sí | Tardó más que el tiempo máximo de ejecución que permite la instancia de Lightning | +| Exception | ⚫ | | Sí | Ocurrió un error que no esperábamos (se notificó al superusuario de la instancia) | +| Lost | ⚫ | | Sí | Lightning perdió la comunicación con el worker (se notificó al superusuario de la instancia) | +| Rejected | ⚪ | | - | El administrador de la instancia no procesará esta solicitud de run porque tu proyecto alcanzó su límite de runs | + +### \*Nota sobre el manejo de errores dentro de un workflow {#note-on-error-handling-within-a-workflow} + +Si un step del workflow falla (por ejemplo, con `JobError`, `TypeError` o +`RangeError`), el worker de OpenFn sigue procesando el workflow, ya que puede +haber reglas de manejo de errores en los edges posteriores. (Por ejemplo: "Si el +step 3 falla, ejecuta el step 4"). + +Si un step falla con un crash (por ejemplo, `SyntaxError`), el worker no puede +ejecutar ninguna lógica posterior y se aborta todo el attempt. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/troubleshooting.md b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/troubleshooting.md new file mode 100644 index 000000000000..69a7459a5b65 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/monitor-history/troubleshooting.md @@ -0,0 +1,181 @@ +--- +title: Logs y solución de problemas +sidebar_label: Logs y solución de problemas +keywords: + - runs + - logs + - log levels + - status codes + - exit codes + - troubleshooting +translation_source_hash: 142023e6bcf41255617e547ab466c497c6e1266e +translation_review_status: machine +--- + +Esta página ofrece consejos para solucionar problemas a quienes usan la +_plataforma OpenFn v2_. + +## Runs + +Una de las páginas más útiles para solucionar problemas en OpenFn es la página +[History](/monitor-history/activity-history.md). Muestra una lista de todos los +runs ejecutados para una work order y su estado. Los administradores del +proyecto pueden investigar los errores haciendo clic en un run para revisar sus +detalles. Más información sobre los runs +[aquí](/monitor-history/inspect-runs.md). + +### Códigos de estado {#status-codes} + +Cada run tiene un código de estado. El código de estado es la forma en que +OpenFn clasifica el estado del run, y puede ayudarte a solucionar errores. Más +información sobre los códigos de estado de OpenFn y lo que significa cada uno +[aquí](/monitor-history/status-codes.md). + +### Cuánto tardó el workflow en fallar {#the-time-it-took-for-the-workflow-to-fail} + +El run también registra cuánto tiempo pasó antes de que el workflow fallara. +Este dato ayuda a saber si el workflow está tardando más de lo que debería, y es +especialmente útil con errores relacionados con tiempos de espera agotados. +Puedes usar el run para saber en qué operación se agota el tiempo del workflow y +si se puede optimizar su rendimiento. + +### Logs del run {#run-logs} + +Mientras desarrollas workflows, es importante registrar en los logs detalles que +harán mucho más fáciles las pruebas y la solución de problemas en el futuro. + +#### Niveles de log {#log-levels} + +![log-levels](/img/log-levels.webp) + +| Nivel | Descripción | +| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `debug` | Muestra todos los logs, incluidas las cosas de nivel de sistema que produce el "runtime" y la salida de una instrucción `console.debug()` escrita por el usuario. | +| `info` | El nivel de log predeterminado. Muestra la información clave que producen los adaptors o las instrucciones `console.log()`/`console.info()`. | +| `warn` | Oculta la mayor parte del ruido y solo muestra los eventos principales del run (inicio y fin de cada step), las advertencias de los adaptors o las instrucciones `console.warn()`. | +| `error` | Oculta todo excepto los eventos principales del run, los errores de los adaptors y las instrucciones `console.error()`. | + +#### Mapeos {#mappings} + +Si es posible, los logs deberían escribirse de forma que se vea exactamente qué +se mapeó entre el sistema de origen y el sistema de destino. En resumen, el log +puede tener una sección **"Datos recibidos del sistema de origen"** y una +sección **"Datos que se cargarán en el sistema de destino"**. + +Estos logs pueden ayudar a los administradores a verificar que los datos de +origen y los datos que se cargan en el sistema de destino son correctos. Por +ejemplo, ver en los logs que un identificador único se está mapeando a +`undefined` en el sistema de destino puede ayudarte a entender la causa raíz de +un error. Este mensaje de error de Salesforce podría deberse a un mapeo a +`undefined`: + +`METHOD_NOT_ALLOWED: HTTP Method 'PATCH' not allowed. Allowed are GET,HEAD,POST at HttpApi.getError`. + +#### Mensajes de error {#error-messages} + +El log del run también debería indicarnos si se lanzó un error y, según el +sistema de destino, cuál es el mensaje de error. A veces el mensaje de error es +muy específico, como: + +`NOT_FOUND: Provided external ID field does not exist or is not accessible` + +Este error de Salesforce suele indicar que `External ID` no está marcado en la +configuración del campo en Salesforce. + +Otros mensajes de error no son tan claros y pueden llevar un tiempo de +depuración: + +`TypeError [Error]: Cannot read property 'split' of undefined` + +Los **`TypeErrors`** suelen indicar que el job recibió una parte de la entrada +que no esperaba, o que hay un error de sintaxis en el código del job. Significa +que hay que actualizar el job para que sepa manejar esa entrada. En este caso, +el job recibió una versión antigua del formulario de CommCare a la que le +faltaba un campo sobre el que el job llamaba a la función `split`. Para +averiguarlo, revisa en qué campos del job se llama a la función split y +comprueba que todos estén presentes en el mensaje. + +Cuanto más pruebas y solucionas problemas con un sistema concreto, más te +familiarizas con sus mensajes de error. + +:::tip + +OpenFn describió varios de los mensajes de error más comunes de algunos de los +sistemas que integramos en el pasado. Explora estos sistemas y sus mensajes de +error [aquí](/adaptors). + +::: + +## Aprovechar la búsqueda y los filtros en OpenFn {#leveraging-search-and-filtering-in-openfn} + +Aprovecha las distintas funcionalidades de búsqueda de OpenFn para encontrar los +runs que te ayuden a solucionar problemas. En la página History puedes buscar +por IDs de OpenFn, entradas o logs. + +Mira este [video](https://youtu.be/XIUykmLCxwQ?si=hquc8rPTJrAZkbbD) para +aprender a usar la búsqueda. + +## Suscríbete a las alertas por correo electrónico {#sign-up-for-email-alerts} + +Puedes activar las notificaciones para recibir +[alertas por correo electrónico](/manage-projects/notifications.md) cuando un +workflow falle y suscribirte a resúmenes de la actividad del proyecto. + +## Más {#more} + +> ¿Qué pasa si los datos de mi encuesta de ODK tienen que vincularse con +> registros existentes en mi sistema Salesforce, pero alguien que responde +> ingresa o selecciona un `external ID` no válido? + +Buena pregunta, y no te preocupes: pasa todo el tiempo. Suponiendo que ya +tomaste todas las medidas posibles para precargar los external IDs en tu +formulario de ODK o para usar IDs más a prueba de errores humanos (como códigos +de barras y huellas digitales), este es el flujo de trabajo: + +1. Lee el correo electrónico e investiga el motivo del fallo. + +2. El 99 % de los runs fallidos en OpenFn se deben a `value mismatches`. El `id` + _recolectado_ en ODK no coincide con el `id` _esperado_ en Salesforce. Ahora + tienes que elegir entre: + + A. Editar el `id` de origen en tu `receipt` y reintentar el attempt. + + B. Editar el `id` relacionado en tu sistema de destino y reintentar el + attempt. + + C. Ignorar el attempt: estos datos de origen nunca llegarán a tu sistema de + destino. (Se reportó que el publicador JSON de ODK Aggregate envía valores + duplicados. Si eso pasa y tu run falla por "valores duplicados" en un campo + único concreto, puedes ignorar el run en OpenFn sin problema). + +Puedes editar los datos de tu sistema de destino desde la interfaz de ese +sistema. Muchas herramientas que actúan como `sources` (como ODK) no facilitan +editar y volver a enviar datos. Puedes usar OpenFn para editar los datos de +origen antes de reintentar el attempt. + +### Mensajes de error comunes {#common-error-messages} + +Estos son los mensajes de error más comunes, con sus explicaciones: + +```sh +DUPLICATE_VALUE: duplicate value found: ODK_uuid__c duplicates value on record with id: a0524000005wNw0 +The insert is blocked because you are attempting to create a new record with a +unique field with the same value as an existing record. +``` + +```sh +Required value missing +``` + +```sh +ExternalId not found +``` + +```sh +{ INVALID_FIELD_FOR_INSERT_UPDATE: Unable to create/update fields: Contact__c. +Please check the security settings of this field and verify that it is +read/write for your profile or permission set. } +``` + +Este último puede aparecer si una relación maestro-detalle en Salesforce no está +configurada como reparentable y el usuario intenta ejecutar un upsert. diff --git a/i18n/es/docusaurus-plugin-content-docs/current/tutorials/commcare-to-db.md b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/commcare-to-db.md new file mode 100644 index 000000000000..0152bbf4105d --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/commcare-to-db.md @@ -0,0 +1,195 @@ +--- +sidebar_label: De CommCare a PostgreSQL +title: Sincroniza los envíos de formularios de CommCare con una base de datos PostgreSQL +translation_source_hash: 9b51cc0ee8f81c1b020b68b3d5226cbfe7a60eab +translation_review_status: machine +--- + +**Antes de empezar este tutorial, asegúrate de lo siguiente:** + +- Te registraste en [OpenFn.org](http://openfn.org) (¡toma menos de un minuto!). +- Revisaste nuestro glosario y conoces la terminología básica de OpenFn y de las + API. Para empezar, consulta estas páginas: + - [Conceptos de OpenFn](/get-started/terminology.md) + - [Glosario de integración de datos](/get-started/glossary.md) +- Tienes una aplicación de CommCare con al menos un formulario configurado. Este + es tu sistema de origen. +- Tienes una base de datos PostgreSQL configurada. Este es tu sistema de + destino. + +**Si no tienes una aplicación de CommCare ni una base de datos PostgreSQL +configuradas, también puedes seguir el tutorial con la solución ya preparada. +Encontrarás todo en estos enlaces:** + +1. [Documento de especificaciones de mapeo](https://docs.google.com/spreadsheets/d/1pi_oxImakhtaCCCIENkjTPZeuyWhpFEcNmH7hfvTBgo/edit?usp=sharing) +2. Aplicación de CommCare para descargar: + - Nombre de usuario: testuser + - Contraseña: 123 + +![install_cc_app](/img/install_cc_app.webp) + +3. [Reporte público con los registros de la base de datos PostgreSQL](https://analytics.openfn.org/public/question/095449a9-5696-463c-a4fb-24614c9f08a5) + +## Primeros pasos {#getting-started} + +En esta guía vamos a configurar una **sincronización automática de datos entre +CommCare y una base de datos PostgreSQL**. Sincronizaremos los envíos de una +aplicación de CommCare llamada `Maternal and Newborn Health`, que tiene el +formulario `Register a New Patient`. + +:::tip + +Cada vez que un usuario de CommCare registre a un paciente nuevo, sus datos se +sincronizarán automáticamente con una base de datos PostgreSQL ya configurada. +Así podrás monitorear y analizar en tiempo real los datos recolectados en campo. +Por ejemplo, puedes conectar rápidamente esta base de datos a un tablero que +muestre datos agregados de los pacientes registrados. + +::: + +![cc-postgres](/img/cc-postgres.webp) + +**Esta integración se divide en dos partes:** + +1. Llevar los datos de tu sistema de origen a OpenFn para disparar tu workflow +2. Transformar estos datos y cargarlos en tu sistema de destino + +¡Empecemos! + +## Obtener datos de CommCare {#getting-data-from-commcare} + +**Hay dos formas de llevar los envíos de formularios de CommCare a OpenFn.** + +### Opción 1: un webhook que reenvía casos o formularios de CommCare a OpenFn en tiempo real con un servicio REST {#option-1-webhook-to-forward-cases-andor-forms-in-real-time-from-commcare-to-openfn-using-rest-service} + +CommCareHQ tiene una función nativa de reenvío de datos: un servicio +webhook/REST que puedes apuntar al destino que elijas (es decir, tu workflow de +OpenFn). Con un webhook configurado, todos los formularios que se envían en +CommCare se **_reenvían automáticamente_** al endpoint indicado, como tu +workflow de OpenFn. Una vez configurado, el reenvío funciona solo, **_en tiempo +real para todos los formularios y casos_**. Aprende a configurar un webhook +[aquí](/adaptors/commcare#webhook-or-data-forwarding-setup-commcare-to-openfn). + +![option1](/img/option1.webp) + +### Opción 2: extraer datos de CommCare con la API REST {#option-2-extracting-commcare-data-via-the-rest-api} + +CommCare ofrece una +[API REST](https://confluence.dimagi.com/display/commcarepublic/List+Forms) +robusta para extraer y cargar datos. Esta segunda opción consiste en configurar +un step en OpenFn que obtenga los envíos de CommCare con una solicitud HTTP +`GET`, con parámetros para filtrar la consulta. Para acceder a la API de +CommCare necesitas un plan de pago de CommCare. + +La principal ventaja del webhook es que tus datos llegan al sistema de destino +en tiempo real. Aun así, la API List Forms también tiene ventajas: permite +extraer datos en lote de forma programada, por ejemplo, para sincronizar datos +históricos el día 30 de cada mes. La opción que elijas depende de las +necesidades de tu organización. + +### Configura un workflow con la opción 1 {#set-up-a-workflow-using-option-1} + +1. **Abre un proyecto existente y crea un workflow nuevo** + +![create_new_workflow](/img/create-new-workflow.gif) + +2. **Crea un trigger "Webhook" nuevo para programar este job de extracción.** + +![create_trigger](/img/create_trigger.gif) + +Asegúrate de copiar en CommCare la URL del webhook de tu workflow de OpenFn. +Cada formulario que se envíe en CommCare llegará automáticamente a OpenFn y +disparará tu nuevo workflow. + +## Transformar y cargar los datos de CommCare en una base de datos PostgreSQL {#transforming-and-loading-commcare-data-to-a-postgresql-database} + +1. **Necesitas una base de datos configurada y un nombre de usuario para que + OpenFn pueda leer y escribir datos en las tablas de destino.** Para esta + demostración, configuramos la base de datos + [así](https://docs.google.com/spreadsheets/d/1pi_oxImakhtaCCCIENkjTPZeuyWhpFEcNmH7hfvTBgo/edit?usp=sharing) + para guardar los datos del formulario de CommCare. Consulta + [esta página](/design/mapping-specs.md) para aprender a crear tu propio + `mapping specification document` y mapear los elementos de datos que se van a + intercambiar. + +![db_config](/img/db_config.webp) + +2. **Crea un step nuevo con el adaptor `postgresql` para cargar los datos de + CommCare en tu base de datos de destino.** + +![configure_job_postgres](/img/create-job.gif) + +3. **Crea una credencial de PostgreSQL, que el step usará para autenticarse con + la base de datos.** + +![add_credential_postgres](/img/postgresql-cred.gif) + +4. **Escribe el step:** en este step usaremos la operación upsert para insertar + o actualizar registros en la tabla de destino `patient`, con `patient_id` + como clave primaria. Un `upsert` actualiza una fila si el valor indicado ya + existe en la tabla, y si no existe, inserta una fila nueva. + +```js +upsert('patient', 'ON CONSTRAINT patient_pk', { + patient_id: dataValue('data.patient_name'), + patient_name: dataValue('data.patient_name'), + village_name: dataValue('data.village_name'), + last_menstrual_period: dataValue('data.last_menstrual_period'), + expected_delivery_date: dataValue('data.expected_delivery_date'), + children_alive: dataValue('data.children_alive'), + living_children: dataValue('data.living_children'), + feeling_sick: dataValue('data.feeling_sick'), + total_children: dataValue('data.Total_children'), + risk_level: dataValue('data.Risk_level'), +}); +``` + +Puedes modificar este código para adaptarlo a tu configuración de CommCare y de +la base de datos, según tus especificaciones de mapeo. + +![create-job](/img/create_job_db.gif) + +## ¡Hora de probar! {#time-to-test} + +1. Envía un formulario en CommCare. +2. Si activaste el reenvío de datos, tu workflow debería dispararse + automáticamente. +3. Si no activaste el reenvío de datos y en su lugar configuraste un step FETCH, + ejecuta el step (revisa que las fechas `received_on_start` y + `received_on_start` del FETCH sean las correctas). +4. Ejecuta el step FETCH. Si funciona, el step "Load to DB" debería ejecutarse + automáticamente. +5. Revisa el `History` y comprueba que la work order se completó con éxito. + +![activity_history_final](/img/activity_history_success.webp) + +:::info + +**Qué hacer si tu run falla:** + +1. Abre el run para revisar el registro de errores. +2. Ajusta el step para resolver el problema y vuelve a ejecutarlo las veces que + haga falta con el botón "rerun" en `History` o con el botón "Re-run from + here" en el `Inspector`. +3. Consulta la página de + [errores comunes de PostgreSQL](/adaptors/postgresql/#common-errors) para ver + más detalles. + +::: + +4. **Por último, actualiza tu base de datos y revisa los datos del nuevo + envío.** + +![metabase](/img/metabase.webp) + +Aunque esta guía es específica para bases de datos PostgreSQL, en general puedes +seguir los mismos pasos con otros tipos de bases de datos (por ejemplo, MS SQL o +MySQL): solo tienes que usar otro adaptor en la configuración del step. + +**Otros recursos que puedes consultar:** + +1. La biblioteca de jobs de OpenFn +2. Las páginas de "App" de CommCare y Postgres en la documentación de OpenFn + +**¿Tienes preguntas, comentarios o ideas nuevas de configuración? Escríbenos en +el foro de la [comunidad de OpenFn](https://community.openfn.org/).** diff --git a/i18n/es/docusaurus-plugin-content-docs/current/tutorials/http-to-googlesheets.md b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/http-to-googlesheets.md new file mode 100644 index 000000000000..0e9305e228ed --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/http-to-googlesheets.md @@ -0,0 +1,177 @@ +--- +sidebar_label: De HTTP a GoogleSheets +title: Workflow de HTTP a GoogleSheets +translation_source_hash: dda6b88cd13c6a33fde0ac1765e545b6c743d03c +translation_review_status: machine +--- + +# Crea un workflow que conecte una API REST con Google Sheets + +En este tutorial te mostramos cómo crear un workflow sencillo de OpenFn que +automatiza la sincronización de datos entre una API REST y Google Sheets, con +los [adaptors](/adaptors) `http` y `GoogleSheets`. + +## Video explicativo {#video-walkthrough} + +Mira el video y sigue los pasos que aparecen abajo. + + + +## Antes de empezar {#before-you-start} + +Antes de comenzar, damos por hecho que revisaste lo siguiente: + +- Revisaste nuestro glosario y conoces los conceptos básicos de OpenFn y de las + API. Para empezar, consulta estas páginas: + - [Conceptos de OpenFn](/get-started/terminology.md) + - [Glosario de integración de datos](/get-started/glossary.md) +- Tienes una cuenta de Google. La usaremos para crear una credencial que + autorice el acceso a Google Sheets. +- Tienes acceso a un proyecto de OpenFn (en una + [aplicación OpenFn v2](https://github.com/OpenFn/lightning) instalada + localmente o en [app.openfn.org](https://app.openfn.org)). + +## Primeros pasos {#getting-started} + +En esta guía vamos a configurar un workflow que **sincronice automáticamente +datos de `user` desde una API REST web y los importe a una GoogleSheet**. + +**Esta integración se divide en dos partes:** + +1. Obtener datos de la API REST (la aplicación de "origen") +2. Transformar e importar estos datos a una tabla de tu GoogleSheet (la + aplicación de "destino") + +¡Empecemos! + +## 1: Crea un workflow nuevo {#1-create-a-new-workflow} + +Para crear un workflow nuevo en tu proyecto: + +1. Ve a la página `project dashboard`. +2. Haz clic en el botón `Create new workflow`. +3. Ponle a tu workflow un `Name` descriptivo (por ejemplo, `Sync Users List`). +4. Elige tu [trigger](/build/triggers.md). +5. Edita tu primer [step](/build/steps/steps.md). + +## 2. Configura tu primer step para obtener datos de la API REST {#2-configure-your-first-step-to-get-data-from-the-rest-api} + +[JSONPlaceholder](https://jsonplaceholder.typicode.com/users) ofrece una API +falsa y gratuita para hacer pruebas y prototipos. Usaremos la +[API REST de usuarios](https://jsonplaceholder.typicode.com/users) para extraer +datos de usuarios. Para eso, configuraremos un step en OpenFn que obtenga esos +datos con una solicitud HTTP `GET`. Haz clic en tu primer step para configurarlo +con estas opciones: + +- Name `Fetch Users` +- Adaptor `http` +- Version: `6.0.0` +- Credentials (opcional: credencial "Raw JSON") - + `{ "baseUrl": "https://jsonplaceholder.typicode.com/"}` +- Código del job: si configuraste la credencial "Raw JSON" con jsonplaceholder + como baseURL, agrega la operación `get("users")` en el bloque de código. + +:::tip ¿Necesitas ayuda para escribir el código del job? + +Consulta la documentación sobre el +[adaptor "http"](/adaptors/packages/http-readme), sobre +[cómo configurar steps](/build/steps/steps.md) y sobre +[cómo escribir jobs](/jobs/job-writing-guide.md). + +::: + +**Cuando termines de configurar y escribir tu step, ¡guárdalo y ejecútalo!** + +- Consulta la [sección de workflows](/build/workflows.md) para ver más + orientación sobre cómo crear y ejecutar workflows. + +**Revisa el panel `Output & Log` para ver si tu run se completó con éxito.** Si +fue así, deberías ver: + +- El estado `success` +- La pestaña Log termina con `Run complete with status: success` +- La pestaña Input muestra `{}` +- La pestaña Output muestra `{ data: [ {...}]}` + +## 3. Configura otro step para transformar los datos e importarlos a tu GoogleSheet {#3-configure-another-step-to-transform-the-data--import-your-googlesheet} + +Crea una `Credential` nueva de Googlesheet con el correo de tu cuenta de Google. +(Asegúrate de que este usuario de Google tenga permiso de edición en la +GoogleSheet que quieres integrar). + +:::info ¿No ves la opción de credencial de GoogleSheets? + +Si el superusuario de tu instancia no configuró un cliente OAuth global, quizás +tengas que configurar uno tú. Consulta +[los clientes OAuth](/manage-projects/oauth.md#oauth-clients) y +[los detalles de un cliente de GoogleSheet](/adaptors/googlesheets#permissions-scopes). + +::: + +Para esta demostración, configuramos la Googlesheet +[así](https://docs.google.com/spreadsheets/d/1gT4cpHSDQp8A_JIX_5lqTLTwV0xBo_u8u3ZNWALmCLc/edit?usp=sharing) +para guardar los datos de `users`. + +Crea un step nuevo con el adaptor `googlesheets` para cargar los datos de los +usuarios en tu GoogleSheet de destino. Configura el step con estas opciones: + +- Name `Sync Users` +- Adaptor `googlesheets` +- Version: `2.2.2` +- Credentials: crea una credencial `GoogleSheet OAuth` nueva y guárdala +- Operaciones del step: en este job usaremos la operación `appendValues()` para + agregar un array de filas a la hoja de cálculo. Asegúrate de cambiar el + `spreadsheetId` por el ID de tu hoja de cálculo. + + ```js + // Prepara el array con los datos de los usuarios + fn(state => { + const users = state.data.map( + ({ id, name, username, address, phone, website, company }) => [ + id, + name, + username, + address.city, + phone, + website, + company.name, + ] + ); + + return { ...state, users }; + }); + + // Agrega los datos de los usuarios a la GoogleSheet + appendValues({ + spreadsheetId: '1gT4cpHSDQp8A_JIX_5lqTLTwV0xBo_u8u3ZNWALmCLc', + range: 'users!A1:G1', + values: state => state.users, + }); + ``` + +- Input - `Final output of Fetch Users` + +Si ya ejecutaste el step `Fetch Users`, tendrás una entrada inicial para probar +el step `Sync Users`. Selecciona la entrada en el panel Input y haz clic en +`Create New Work Order` para ejecutar este step. + +## 4. ¡Hora de probar! {#4-time-to-test} + +1. Selecciona el step `Fetch Users` y ábrelo en el Inspector. +2. Crea una entrada nueva vacía `{}`. +3. Haz clic en `Create New Work Order` para ejecutar el step. +4. Revisa los resultados en el panel `Output & Logs` y comprueba que los dos + steps terminaron con el estado `success`. +5. Por último, revisa tu hoja de cálculo para ver los datos de usuarios + sincronizados. + +¿Te aparecen errores o no sabes cómo seguir? Consulta la documentación sobre +[workflows](/build/workflows.md) o sobre +[solución de problemas](/monitor-history/troubleshooting.md). + +:::tip ¿No puedes avanzar? ¿Tienes preguntas? + +Recuerda [ver el video](#video-walkthrough) o publicar en la +[comunidad](https://community.openfn.org) para pedir ayuda. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/tutorials/kobo-to-dhis2.md b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/kobo-to-dhis2.md new file mode 100644 index 000000000000..22552a0d93c6 --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/kobo-to-dhis2.md @@ -0,0 +1,226 @@ +--- +sidebar_label: De Kobo a DHIS2 +title: Workflow de reportes de Kobo a DHIS2 +slug: /kobo-to-dhis2 +translation_source_hash: b39aafc3fe9d4c25cde774798c8f9cfb06f0b082 +translation_review_status: machine +--- + +# Crea un workflow que automatice los reportes entre KoboToolbox y DHIS2 + +En este tutorial te mostramos cómo crear un workflow sencillo de OpenFn que +automatiza los reportes entre [KoboToolbox](https://www.kobotoolbox.org/) (una +aplicación móvil de recolección de datos) y [DHIS2](https://dhis2.org) (un +sistema de información de salud muy usado para reportar datos agregados sobre +indicadores clave), con los [adaptors](/adaptors) `kobotoolbox` y `dhis2`. + +### Video explicativo {#video-walkthrough} + +:::tip Tutorial de introducción a workflows y History + +Mira este +[tutorial de introducción a workflows y History](https://youtu.be/hae8eM0iYnM?si=LGbv1TK0W9L9y12u) +para que te guíe en la configuración de este workflow. + +::: + +### Resumen del workflow {#workflow-overview} + +Este workflow de OpenFn tendrá 3 steps: + +1. Obtener los envíos de formularios de Kobotoolbox +2. Contar cuántos valores `OPV0_dose_given` hay en los envíos, para saber + cuántos beneficiarios recibieron la vacuna OPV0 +3. Importar los resultados agregados a DHIS2 para reportar el número de dosis + registradas esa semana + +### Requisitos previos {#prerequisites} + +- Tienes un proyecto de OpenFn. +- Tienes una cuenta de KoboToolbox y un formulario para sincronizar (más abajo + encontrarás credenciales de demostración). +- Tienes los datos de acceso a una instancia de DHIS2 (más abajo encontrarás los + de la instancia "play" de DHIS2). + +### Step 1: Obtener los envíos del formulario de Kobo {#step-1-get-kobo-form-submission} + +Crea el primer step en el Canvas del workflow. + +- Name: `Get Kobo Form Submission` +- Adaptor: `kobotoolbox` +- Version: `latest` +- Credential: ver abajo + +Este step usa el adaptor kobotoolbox con la siguiente configuración de +credencial: + +```json +{ + "baseURL": "https://kf.kobotoolbox.org", + "username": "openfn_demo", + "password": "openfn_demo", + "apiVersion": "v2" +} +``` + +En este step queremos obtener los envíos del formulario de demostración con el +ID `aBpweTNdaGJQFb5EBBwUeo`. Para eso, abre el +[editor del Inspector](/build/steps/step-editor.md) y agrega el siguiente código +del job: + +```javascript +// Step 1: obtiene los envíos del formulario de Kobotoolbox +getSubmissions({ formId: 'aBpweTNdaGJQFb5EBBwUeo' }); +``` + +:::tip ¿Necesitas ayuda para escribir el código del job? + +Consulta la documentación sobre el +[adaptor "kobotoolbox"](/adaptors/kobotoolbox), sobre +[cómo configurar steps](/build/steps/steps.md) y sobre +[cómo escribir jobs](/jobs/job-writing-guide.md). + +::: + +#### Explicación {#explanation} + +- `getSubmissions`: obtiene los envíos de formularios. +- `{ formId: "aBpweTNdaGJQFb5EBBwUeo" }`: indica el ID del formulario del que se + obtienen los envíos. + +#### Prueba {#testing} + +Crea una entrada vacía `{}` y haz clic en el botón `Create New Work Order` para +ejecutar el workflow. Consulta [la documentación](/build/workflows.md) para +saber más sobre cómo ejecutar workflows manualmente. + +El `output` esperado debería contener 17 registros en `state.data.results`. + +### Step 2: Contar las dosis de OPV aplicadas {#step-2-count-opv-dose-given} + +Crea un segundo step después de `Get Kobo Form Submission` así: + +- Name: `Count OPV Dose Given` +- Adaptor: `common` (se usa cuando quieres agregar funciones de JavaScript + personalizadas) +- Version: `latest` +- Credential: no hace falta + +En este step vamos a contar todos los registros con `"OPV0_dose_given": "yes"`. +Para agregar esta lógica, abre el [Inspector](/build/steps/step-editor.md) y +agrega el siguiente código del job en el editor: + +```javascript +// Filtra y cuenta las dosis de OPV aplicadas +fn(state => { + const opvDosesGivenCount = state.data.results.filter( + r => r['OPV0_dose_given'] === 'yes' + ).length; + + return { ...state, opvDosesGivenCount }; +}); +``` + +:::tip ¿Necesitas ayuda para escribir el código del job o para cambiar esta +lógica? + +Consulta la documentación sobre el +[adaptor "common"](/adaptors/packages/common-docs), sobre +[cómo configurar steps](/build/steps/steps.md) y sobre +[cómo escribir jobs](/jobs/job-writing-guide.md). + +::: + +#### Explicación {#explanation-1} + +- `fn`: una función de OpenFn que da más flexibilidad al escribir jobs. Te + permite hacer algo con el state y devolver los datos transformados al state. +- `opvDosesGivenCount`: cuenta cuántas veces aparece "yes" en el campo + `OPV0_dose_given`. + +#### Prueba {#testing-1} + +Selecciona el primer step, `Get Kobo Form Submission`, y haz clic en +`Create New Work Order` con una entrada vacía (consulta la +[documentación de workflows](/build/workflows.md) si necesitas ayuda para +ejecutar y probar steps). Los dos steps deberían ejecutarse con éxito y en el +state final deberías ver que se agregó `opvDosesGivenCount: 3`. + +### Step 3: Mapear y cargar en DHIS2 {#step-3-map-and-load-to-dhis2} + +Crea un tercer step después de `Count OPV Dose Given` así: + +- Name: `Map and Load to DHIS2` +- Adaptor: `dhis2` +- Version: `v4.0.3` +- Credential: una credencial `dhis2` nueva con la siguiente configuración + +```json +{ + "hostUrl": "https://play.dhis2.org/dev", + "username": "admin", + "password": "district" +} +``` + +En este step queremos agregar la lógica para importar `dataValues` a DHIS2 y así +"reportar" el total de dosis de la vacuna OPV0 que calculamos en el step 2. + +Para eso, abre el [Inspector](/build/steps/step-editor.md) y agrega el siguiente +código del job en el editor: + +```javascript +// Importa a DHIS2 +create('dataValueSets', state => ({ + dataSet: 'BfMAe6Itzgt', // Child Health + period: '202402', // Feb 2024 + orgUnit: 'DiszpKrYNg8', // Ngelehun CHC + dataValues: [ + { + categoryOptionCombo: 'Prlt0C1RF0s', //Fixed <1yr + dataElement: 'x3Do5e7g4Qo', // OPV0 doses given + value: state.opvDosesGivenCount, //# of OPV0 doses given + }, + ], +})); +``` + +:::tip ¿Necesitas ayuda para escribir el código del job o para cambiar esta +lógica? + +Consulta la documentación sobre el [adaptor "dhis2"](/adaptors/dhis2), sobre +[cómo configurar steps](/build/steps/steps.md) y sobre +[cómo escribir jobs](/jobs/job-writing-guide.md). + +::: + +#### Explicación {#explanation-2} + +- `create('dataValueSets', {...})`: esta función de OpenFn crea un datavalueset + nuevo en DHIS2. +- `dataSet`, `completeDate`, `period`, `orgUnit`: los detalles del datavalueset. +- `dataValues`: un array con los elementos de datos y sus valores. + +#### Prueba {#testing-2} + +Guarda los cambios, ve al primer step (Get Kobo Form Submission), crea una +entrada vacía `{}` y haz clic en el botón `Create New Work Order` para ejecutar +el workflow. Todos los steps deberían ejecutarse con éxito y deberías ver +actualizado `OPV0 doses given` en DHIS2. Consulta la +[documentación de workflows](/build/workflows.md) si necesitas ayuda para +ejecutar o probar workflows. + +### Conclusión {#conclusion} + +¡Felicitaciones! Creaste un workflow de OpenFn que automatiza todo el proceso: +obtiene los envíos de formularios de Kobotoolbox, calcula el total de dosis de +OPV aplicadas a los beneficiarios y reporta ese total a DHIS2 como `dataValues`. + +:::tip ¿No puedes avanzar? ¿Tienes preguntas? + +Mira este +[tutorial de introducción a workflows y History](https://youtu.be/hae8eM0iYnM?si=LGbv1TK0W9L9y12u) +o publica tus preguntas en la [comunidad](https://community.openfn.org) para +recibir ayuda. + +::: diff --git a/i18n/es/docusaurus-plugin-content-docs/current/tutorials/tutorial.md b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/tutorial.md new file mode 100644 index 000000000000..de69be9ace0f --- /dev/null +++ b/i18n/es/docusaurus-plugin-content-docs/current/tutorials/tutorial.md @@ -0,0 +1,43 @@ +--- +title: Tutorial +sidebar_label: Guía rápida de workflows +translation_source_hash: ee5eee9bd2e17a736919bba3e005069daac0a44f +translation_review_status: machine +--- + +# Tutorial: crea tu primer workflow + +# Guía rápida: crea tu primer workflow + +1. Ve a tu proyecto de OpenFn > `Workflows`. +2. Crea un [workflow](/build/workflows.md) nuevo. +3. Elige el [tipo de trigger](/build/triggers.md): Webhook Event (para + integraciones en tiempo real) o Cron Expression (para integraciones + programadas). +4. Ponle nombre a tu primer `Step` (por ejemplo, "Import form submission") y + ábrelo para elegir el [adaptor](/adaptors), la `Version` del adaptor y la + [credencial](/build/credentials.md). +5. Haz clic en el botón de código `` para abrir el + [Inspector](/build/steps/step-editor.md) y agrega el código del job en el + panel `Editor` para definir la lógica de negocio o las reglas de + transformación de este workflow. +6. En el panel `Input` de la izquierda, agrega una entrada personalizada (por + ejemplo, el payload de una solicitud de webhook) o simplemente agrega llaves + vacías (`{}`) para ejecutar un workflow con un trigger cron. Consulta la + [documentación de workflows](/build/workflows.md) si necesitas ayuda para + ejecutar y probar workflows. +7. Si el step funciona, vuelve a la vista del Canvas y haz clic en el ícono `+` + para agregar un segundo step. +8. Si quieres definir condiciones para decidir si este segundo step se ejecuta y + cuándo, actualiza la [condición del path](/build/paths.md). +9. Luego repite los pasos 3 a 6 para terminar de configurar este step, hasta + completar el workflow. + +:::tip + +Mira el video y la documentación de la +[página de workflows](/build/workflows.md) en la sección `Build` para obtener +ayuda detallada, o haz tus preguntas en la +[comunidad](https://community.openfn.org). + +::: diff --git a/i18n/es/docusaurus-theme-classic/footer.json b/i18n/es/docusaurus-theme-classic/footer.json new file mode 100644 index 000000000000..0279f041812f --- /dev/null +++ b/i18n/es/docusaurus-theme-classic/footer.json @@ -0,0 +1,42 @@ +{ + "link.title.This Site": { + "message": "Este sitio", + "description": "The title of the footer links column with title=This Site in the footer" + }, + "link.title.Community": { + "message": "Comunidad", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.title.More": { + "message": "Más", + "description": "The title of the footer links column with title=More in the footer" + }, + "link.item.label.Articles": { + "message": "Artículos", + "description": "The label of footer link with label=Articles linking to articles" + }, + "link.item.label.Adaptors": { + "message": "Adaptors", + "description": "The label of footer link with label=Adaptors linking to adaptors" + }, + "link.item.label.Forum": { + "message": "Foro", + "description": "The label of footer link with label=Forum linking to https://community.openfn.org" + }, + "link.item.label.Stack Overflow": { + "message": "Stack Overflow", + "description": "The label of footer link with label=Stack Overflow linking to https://stackoverflow.com/questions/tagged/openfn" + }, + "link.item.label.Twitter": { + "message": "Twitter", + "description": "The label of footer link with label=Twitter linking to https://twitter.com/openfn" + }, + "link.item.label.OpenFn.org": { + "message": "OpenFn.org", + "description": "The label of footer link with label=OpenFn.org linking to https://www.openfn.org" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/openfn" + } +} diff --git a/i18n/es/docusaurus-theme-classic/navbar.json b/i18n/es/docusaurus-theme-classic/navbar.json new file mode 100644 index 000000000000..f56684752de1 --- /dev/null +++ b/i18n/es/docusaurus-theme-classic/navbar.json @@ -0,0 +1,22 @@ +{ + "title": { + "message": "OpenFn", + "description": "The title in the navbar" + }, + "logo.alt": { + "message": "OpenFn", + "description": "The alt text of navbar logo" + }, + "item.label.Docs": { + "message": "Documentación", + "description": "Navbar item with label Docs" + }, + "item.label.Adaptors": { + "message": "Adaptors", + "description": "Navbar item with label Adaptors" + }, + "item.label.Articles": { + "message": "Artículos", + "description": "Navbar item with label Articles" + } +} diff --git a/src/components/UntranslatedNotice.js b/src/components/UntranslatedNotice.js new file mode 100644 index 000000000000..56da9e43dce5 --- /dev/null +++ b/src/components/UntranslatedNotice.js @@ -0,0 +1,27 @@ +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Translate from '@docusaurus/Translate'; + +// Translated files live under i18n/; any other source is the English fallback. +export function isUntranslated(source) { + return !source.startsWith('@site/i18n/'); +} + +// Shown on a non-English locale when the page has no translation and +// Docusaurus has fallen back to the English source file. +export default function UntranslatedNotice({ source }) { + const { i18n } = useDocusaurusContext(); + if (i18n.currentLocale === i18n.defaultLocale) return null; + if (!isUntranslated(source)) return null; + return ( +
+ + + This page isn't available in your language, so we're showing it in + English. + +
+ ); +} diff --git a/src/theme/BlogPostItem/index.js b/src/theme/BlogPostItem/index.js new file mode 100644 index 000000000000..43de4aff5f44 --- /dev/null +++ b/src/theme/BlogPostItem/index.js @@ -0,0 +1,13 @@ +import BlogPostItem from '@theme-original/BlogPostItem'; +import { useBlogPost } from '@docusaurus/plugin-content-blog/client'; +import UntranslatedNotice from '@site/src/components/UntranslatedNotice'; + +export default function BlogPostItemWrapper(props) { + const { metadata, isBlogPostPage } = useBlogPost(); + return ( + <> + {isBlogPostPage && } + + + ); +} diff --git a/src/theme/BlogPostItems/index.js b/src/theme/BlogPostItems/index.js new file mode 100644 index 000000000000..03ddead2dc44 --- /dev/null +++ b/src/theme/BlogPostItems/index.js @@ -0,0 +1,19 @@ +import BlogPostItems from '@theme-original/BlogPostItems'; +import UntranslatedNotice, { + isUntranslated, +} from '@site/src/components/UntranslatedNotice'; + +// Article lists: one notice at the top if any article on the page is untranslated. +export default function BlogPostItemsWrapper(props) { + const untranslated = props.items.find(({ content }) => + isUntranslated(content.metadata.source) + ); + return ( + <> + {untranslated && ( + + )} + + + ); +} diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js new file mode 100644 index 000000000000..6bc630cd1fb6 --- /dev/null +++ b/src/theme/DocItem/Content/index.js @@ -0,0 +1,16 @@ +import Content from '@theme-original/DocItem/Content'; +import { useDoc } from '@docusaurus/plugin-content-docs/client'; +import UntranslatedNotice from '@site/src/components/UntranslatedNotice'; + +export default function ContentWrapper(props) { + const { metadata } = useDoc(); + return ( + <> + {/* v1 docs are frozen and stay English; they already have their own banner. */} + {metadata.version !== 'legacy' && ( + + )} + + + ); +} diff --git a/translation-rules.yml b/translation-rules.yml index d8f36840b1ad..769f94f2f23d 100644 --- a/translation-rules.yml +++ b/translation-rules.yml @@ -58,3 +58,90 @@ rules: reason: '"Actualiza tu plan" reads as "update your plan".' added_by: lmac-1 added_on: 2026-10-01 + + - locale: es + kind: term + source: PII + target: información de identificación personal (PII) + instruction: >- + Write "información de identificación personal" and keep "PII" next to it, + so readers can match it to the English term. + example_source: Remove any data that should not be output (like PII). + example_target: >- + Elimina los datos que no deberían formar parte de la salida (como + información de identificación personal o PII). + reason: The Write Jobs pages translated PII two different ways. + added_by: lmac-1 + added_on: 2026-10-01 + + - locale: es + kind: term + source: core team + target: equipo principal + instruction: '"Core team" is "equipo principal", not "equipo central".' + example_source: Ask the OpenFn core team. + example_target: Pregunta al equipo principal de OpenFn. + reason: Every other Spanish page already uses "equipo principal". + added_by: lmac-1 + added_on: 2026-10-01 + + - locale: es + kind: avoid + source: feature (on pages about code) + target: función + instruction: >- + On pages that also talk about JavaScript or adaptor functions, translate + "feature" as "característica" or "funcionalidad", not "función". Elsewhere, + "función" is fine and usually the most natural word. + example_source: OpenFn supports all modern JavaScript features. + example_target: OpenFn admite todas las características modernas de JavaScript. + reason: >- + On developer pages "función" reads as a JavaScript function, not a + feature. On other pages "función" is what Latin American apps say. + added_by: lmac-1 + added_on: 2026-10-01 + + - locale: es + kind: avoid + source: enable / disable + target: activar / desactivar + instruction: >- + "Enable" and "disable" (a workflow, trigger, setting, or button) are + "habilitar" and "deshabilitar". Keep "activar" and "desactivar" for "turn + on", "turn off", and flipping a toggle. + example_source: >- + Workflows will run automatically when they are "enabled", i.e., when their + trigger is turned on. + example_target: >- + Los workflows se ejecutan automáticamente cuando están "habilitados", es + decir, cuando su trigger está activado. + reason: >- + The Sandboxes page used "desactivados" for triggers that the Workflows and + Triggers pages call "deshabilitados". + added_by: lmac-1 + added_on: 2026-10-02 + + - locale: es + kind: term + source: Expand to see + target: Expande para ver + instruction: >- + In dropdown summaries, "Expand to see..." is "Expande para ver...", not + "Despliega para ver...". + example_source: Expand to see the expected output + example_target: Expande para ver la salida esperada + reason: The CLI pages used "Despliega" on two pages and "Expande" on another. + added_by: lmac-1 + added_on: 2026-10-02 + + - locale: es + kind: avoid + source: run (the verb) + target: correr + instruction: >- + "Run" a job, step, workflow, or command is "ejecutar", not "correr". + example_source: A workflow is the execution plan for running several steps. + example_target: Un workflow es el plan de ejecución de varios steps. + reason: The CLI walkthrough mixed "correr" with "ejecutar" for the same verb. + added_by: lmac-1 + added_on: 2026-10-02 From c637ea7f9b3527a83294cb8aefc5a65f2ddb0ffb Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Tue, 6 Oct 2026 11:13:21 +0100 Subject: [PATCH 33/34] Fix the Spanish roadmap anchors that included link URLs The old anchor one-liner slugged the raw markdown, so the four "See ..." headings got anchors containing their GitHub URLs. --- .../current/contribute/roadmap.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md b/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md index 2dac73e664a8..cf21b78f98c8 100644 --- a/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md +++ b/i18n/es/docusaurus-plugin-content-docs/current/contribute/roadmap.md @@ -50,13 +50,13 @@ Todo el trabajo de nuestro equipo se sigue públicamente en un GitHub Project. Tres vistas clave te muestran al minuto qué estamos haciendo y qué hay en nuestra hoja de ruta inmediata. -### Consulta [**_Now_**](https://github.com/orgs/OpenFn/projects/3/views/24?layout=table&sortedBy%5Bdirection%5D=desc&sortedBy%5BcolumnId%5D=Status) 🚧 para ver lo que se está construyendo ahora {#see-_now_httpsgithubcomorgsopenfnprojects3views24layouttablesortedby5bdirection5ddescsortedby5bcolumnid5dstatus--for-whats-currently-being-built} +### Consulta [**_Now_**](https://github.com/orgs/OpenFn/projects/3/views/24?layout=table&sortedBy%5Bdirection%5D=desc&sortedBy%5BcolumnId%5D=Status) 🚧 para ver lo que se está construyendo ahora {#see-now--for-whats-currently-being-built} -### Consulta [**_Next_**](https://github.com/orgs/OpenFn/projects/3/views/2?layout=table&sortedBy%5Bdirection%5D=desc&sortedBy%5BcolumnId%5D=Status) ⏭️ para ver lo que se está considerando para el próximo sprint {#see-_next_httpsgithubcomorgsopenfnprojects3views2layouttablesortedby5bdirection5ddescsortedby5bcolumnid5dstatus-️-for-whats-being-considered-for-the-next-sprint} +### Consulta [**_Next_**](https://github.com/orgs/OpenFn/projects/3/views/2?layout=table&sortedBy%5Bdirection%5D=desc&sortedBy%5BcolumnId%5D=Status) ⏭️ para ver lo que se está considerando para el próximo sprint {#see-next-️-for-whats-being-considered-for-the-next-sprint} -### Consulta [**_Epics_**](https://github.com/orgs/OpenFn/projects/3/views/7) 🤔 para ver una lista de proyectos que estamos considerando, con una prioridad aproximada {#see-_epics_httpsgithubcomorgsopenfnprojects3views7--for-a-list-of-projects-that-were-considering-roughly-prioritized} +### Consulta [**_Epics_**](https://github.com/orgs/OpenFn/projects/3/views/7) 🤔 para ver una lista de proyectos que estamos considerando, con una prioridad aproximada {#see-epics--for-a-list-of-projects-that-were-considering-roughly-prioritized} -### Consulta [**_Bugs_**](https://github.com/orgs/OpenFn/projects/3/views/22) 🐞 para ver los bugs conocidos que estamos siguiendo {#see-_bugs_httpsgithubcomorgsopenfnprojects3views22--for-known-bugs-were-tracking} +### Consulta [**_Bugs_**](https://github.com/orgs/OpenFn/projects/3/views/22) 🐞 para ver los bugs conocidos que estamos siguiendo {#see-bugs--for-known-bugs-were-tracking} Actualizaremos este sitio cada mes para reflejar nuestro progreso en los temas principales. También puedes seguir en tiempo real todas las funciones nuevas, From e261053d0e80a73b60925489298a301ff42b77fe Mon Sep 17 00:00:00 2001 From: Lucy Macartney Date: Tue, 6 Oct 2026 11:13:21 +0100 Subject: [PATCH 34/34] Tidy the translate skills after the Spanish rollout Drop the unused needs-review status, say what to do when an English page moves or is deleted, and run /translate interface for any text in src/. --- .agents/skills/review-translation/SKILL.md | 6 +++--- .agents/skills/translate/SKILL.md | 4 ++-- .agents/skills/translate/pages.md | 15 +++++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.agents/skills/review-translation/SKILL.md b/.agents/skills/review-translation/SKILL.md index 6eee7c3bb22c..db0d76de6164 100644 --- a/.agents/skills/review-translation/SKILL.md +++ b/.agents/skills/review-translation/SKILL.md @@ -21,9 +21,9 @@ in `.agents/skills/translate/SKILL.md`, the house style in `.agents/skills/translate/.md`, the rules for the locale in `translation-rules.yml`, and `glossary.yml`. -The review fixes clear problems in `machine` and `needs-review` pages and -reports the rest. It does not commit, open a PR, or add rules. Never edit a -`human-reviewed` page or a fenced block; report the problem instead. +The review fixes clear problems in `machine` pages and reports the rest. It does +not commit, open a PR, or add rules. Never edit a `human-reviewed` page or a +fenced block; report the problem instead. ## 1. Lay out the pages diff --git a/.agents/skills/translate/SKILL.md b/.agents/skills/translate/SKILL.md index 91fef3959e63..de56f512fd31 100644 --- a/.agents/skills/translate/SKILL.md +++ b/.agents/skills/translate/SKILL.md @@ -25,8 +25,8 @@ need. covers every page that needs it. See `pages.md`. - **`/translate interface`** translates the text that is not in a page: the navbar, footer, sidebar headings, and homepage. Run it when - `sidebars-main.js`, the navbar or footer in `docusaurus.config.js`, or the - homepage in `src/pages/` has changed. See `interface.md`. + `sidebars-main.js`, the navbar or footer in `docusaurus.config.js`, or any + `` text in `src/` has changed. See `interface.md`. If you are not told which task, work out which ones are needed from what has changed in English, say so, and ask before starting. diff --git a/.agents/skills/translate/pages.md b/.agents/skills/translate/pages.md index 3d9ad7826e68..23f3cbb057fc 100644 --- a/.agents/skills/translate/pages.md +++ b/.agents/skills/translate/pages.md @@ -27,16 +27,16 @@ translation_review_status: machine The hash is the English file's content hash, not a commit, because commits do not survive squash merges. -`translation_review_status` can be `machine`, `needs-review`, or -`human-reviewed`. Only a human sets `human-reviewed`, and adds -`translation_reviewer` and `translation_review_date` with it. +`translation_review_status` is `machine` or `human-reviewed`. Only a human sets +`human-reviewed`, and adds `translation_reviewer` and `translation_review_date` +with it. ## Decide what to do with each page - **No translation yet.** Translate the whole page. To retranslate a page from scratch on purpose, delete it first. - **The hash matches the current English.** Skip it, unless the page is - `machine` or `needs-review` and this lists any commits: + `machine` and this lists any commits: ```bash git log --oneline $(git log -1 --format=%H -- )..HEAD -- glossary.yml translation-rules.yml .agents/skills/translate/.md @@ -44,14 +44,17 @@ not survive squash merges. Then see "When the rules change". -- **The hash does not match, and the status is `machine`, `needs-review`, or - missing.** See "Updating a page". +- **The hash does not match, and the status is `machine` or missing.** See + "Updating a page". - **The hash does not match, and the status is `human-reviewed`.** Leave it out of the translation PR. Open a separate PR for the named reviewer: diff the English they saw against the current English as in "Updating a page", and translate only what changed. Set the new hash and leave the status as `human-reviewed`; the reviewer merging it approves it. If the old English is no longer in the repo, say so and offer a full retranslation. +- **The English page has moved or been deleted.** Move the translation to match + with `git mv`, or delete it. Docusaurus silently ignores a translation with no + English page. ## When the rules change