{
  "version": "1.0",
  "generated": "2026-09-22T23:33:10.210Z",
  "site": {
    "title": "Rafael Pereira — Senior Software Engineer",
    "description": "Institutional portfolio and editorial hub of Rafael Pereira, senior software engineer: proof-first case studies, project artifacts, technical articles, experience narratives, resume, and contact.",
    "url": "https://imrafaeldev.site"
  },
  "entries": [
    {
      "id": "00560b1bff12778f",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 7)",
      "content": "@Post()\n  async createUser(@Body() user: UserDto): Promise<UserEntity> {\n    return this.service.createUser(user.name);\n  }\n\n  @Put(\":id\")\n  async updateUser(\n    @Param(\"id\") id: string,\n    @Body() user: UserDto,\n  ): Promise<UserEntity> {\n    return this.service.updateUser(id, user.name);\n  }\n\n  @Delete(\":id\")\n  async deleteUser(@Param(\"id\") id: string): Promise<void> {\n    return this.service.deleteUser(id);\n  }\n}\n```\n\nDTO:\n\n```typescript\n// infra/dtos/UserDto.ts\nexport class UserDto implements UserEntityProps {\n  id?: string;\n  name: string;\n\n  constructor(props: UserEntityProps) {\n    this.id = props.id;\n    this.name = props.name;\n  }\n}\n```\n\nMódulo de DI:\n\n```typescript\n// infra/modules/UserModule.ts\n@Module({\n  imports: [],\n  controllers: [UserController],\n  providers: [\n    UserService,\n    { provide: CreateUser, useClass: CreateUserUsecase },\n    { provide: UpdateUser, useClass: UpdateUserUsecase },\n    { provide: DeleteUser, useClass: DeleteUserUsecase },\n    { provide: GetUser, useClass: GetUserUsecase },\n    { provide: CreateUserProtocol, useClass: UserPrismaRepository },\n    { provide: UpdateUserProtocol, useClass: UserPrismaRepository },\n    { provide: DeleteUserProtocol, useClass: UserPrismaRepository },\n    { provide: GetUserByNameProtocol, useClass: UserPrismaRepository },\n    { provide: GetUserByIdProtocol, useClass: UserPrismaRepository },\n  ],\n})\nexport class UserModule {}\n```\n\nInfra es más \"libre\": depende de las herramientas y existe para sostener el Core.\n\n## Flujo de ejecución\n\nController (Infra) → Service (Adapter) → Usecase (Core) → Protocol (contrato) → Repository (Adapter) → base (Infra). El usecase no importa el repository concreto; el service no importa la clase concreta del usecase. La flecha de dependencia apunta hacia dentro.\n\n## CRUD complementario\n\nAdemás del registro:\n\n### Búsqueda\n\n```typescript\n// core/features/GetUser.ts\nexport abstract class GetUser {\n  abstract execute(id: string): Promise<UserEntity>;\n}",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "00911f87546a97b0",
      "url": "https://imrafaeldev.site/en/articles/backend-performance-close-to-data",
      "title": "Most backend performance problems start close to the data (Part 2)",
      "content": "That is what happened with a dashboard I worked on. It loaded synchronously, and a single query did everything at once: fetched data, joined several pieces of information, and computed the displayed values. Since database CPU ran very high, we almost doubled CPU and RAM.\n\nThe database got bigger. The dashboard still took over a minute to load. The same query still concentrated all heavy work in a single execution.\n\nThat was when we opened the execution plan. The screen returned few indicators, but the query crossed relationships, formed a large intermediate volume, and spent CPU on aggregations before reaching them. The final answer was small. The work to produce it, enormous.\n\nDoubling CPU and RAM had given the database more headroom, but the search still forced it to do everything at once. The plan showed the investigation had to enter the path traveled by the query.\n\n## What I need to see before touching the database\n\nFor the database to become a real suspect, I want the execution plan and metrics pointing that way. Query time, reads, and cardinality usually confirm or eliminate a hypothesis in minutes.\n\nIf those measures are healthy, I rule the database out. I have caught slow APIs with the query responding within expectations, while the delay sat in application processing and sequential remote calls. From there, continuing to hunt a database defect would only insist on the wrong layer.\n\nBefore going deeper, I run a short radar over the query and the code. One query to load the list followed by another per record calls for a query count. That N+1 shows up often when the ORM leaves relationships for the backend to fetch one by one.\n\nA JOIN multiplying rows before aggregation calls for measuring intermediate volume. A missing index calls for the plan. A filter applying a function over the indexed column too.\n\nIn code, I look for remote calls or queries with await inside a for. Each wait may look small alone and still dominate total time when all run in a queue.",
      "description": "Before adding machines, cache, or queues, measure the request",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "before",
        "with",
        "where",
        "start",
        "column",
        "data"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/en/articles/backend-performance-close-to-data"
      }
    },
    {
      "id": "009b617915f970ed",
      "url": "https://imrafaeldev.site/es/curriculum",
      "title": "Currículum (Part 2)",
      "content": "- mar/2025 a jun/2025 Remoto Azify Ingeniero Backend Senior — Consultoría Expandir experiencia Replegar experiencia\nDesarrollé un motor de liquidación con NestJS y reduje un 30% la latencia de APIs financieras críticas mediante profiling y optimización.\n\nContexto\n\nTrabajé en fintech y criptoactivos, en servicios financieros donde la consistencia, la seguridad y la estabilidad tenían impacto directo en la operación.\n\nContribución\n\nDesarrollé un motor de liquidación con NestJS e integraciones financieras y reduje un 30% la latencia de APIs críticas mediante profiling y optimización.\n\n[Leer la experiencia completa](/es/experiencias/azify/)\n\n- oct/2023 a feb/2025 Remoto VBET Ingeniero Backend Senior Expandir experiencia Replegar experiencia\nApliqué Go, goroutines y channels para reducir la primera etapa del cálculo de comisiones de cerca de siete a tres minutos, y también contribuí a la aplicación React.\n\nContexto\n\nEl producto de analítica atendía a influencers y afiliados de iGaming; su dashboard reunía métricas de comisión y aceptaba un pequeño desfase para los datos del día, mientras que los pagos requerían datos consolidados.\n\nContribución\n\nReestructuré la API y el cálculo con Go, goroutines y channels, introduje ETL, precálculo y caché y colaboré en la aplicación React. La línea medida pasó de cerca de siete minutos en el pico a cerca de tres minutos, cerca de un minuto, aproximadamente 15 segundos sin caché y menos de un segundo con caché caliente, cada valor correspondiente a su etapa.\n\n[Leer la experiencia completa](/es/experiencias/vbet/)\n\n- abr/2023 a oct/2023 Remoto Maxmilhas Ingeniero de Software Full Stack Expandir experiencia Replegar experiencia\nDesarrollé microservicios en Node.js y NestJS para automatizar cancelaciones y cambios, reduciendo un 34% la intervención manual del soporte.\n\nContexto\n\nTrabajé en flujos posventa de viajes, incluyendo cancelaciones, cambios, cupones y comunicación con clientes.\n\nContribución",
      "description": "Currículum online de Rafael Pereira, ingeniero backend senior con experiencia en Node.js, Go y Java, y trabajo complementario con React y Angular.",
      "keywords": [
        "experiencia",
        "nestjs",
        "para",
        "ingeniero",
        "node",
        "backend",
        "remoto",
        "expandir",
        "replegar",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/es/curriculum"
      }
    },
    {
      "id": "00b289dc1a197ff6",
      "url": "https://imrafaeldev.site/experiences/eds-policia-civil-rio-es",
      "title": "EDS y Policía Civil de Río de Janeiro | Rafael Pereira",
      "content": "## Contexto\n\nEn mi consultoría para EDS, trabajé en sistemas destinados a la Policía Civil de Río de Janeiro. El contexto incluía una operación pública crítica, un sistema de gestión de salud de alto volumen y un ERP jurídico en evolución.\n\n## Cómo trabajé\n\nEstructuré el backend del sistema de salud con NestJS y SQL Server. También refactoricé rutas legadas y participé en flujos de automatización de procesos, gestión documental y recolección de evidencias. Seguridad, control de acceso, trazabilidad y cumplimiento de LGPD orientaban cómo debía evolucionar cada ruta.\n\nAdemás del backend, colaboré en componentes compartidos del design system para alinear contratos de API con las interfaces usadas en la operación.\n\n## Lo que me llevé\n\nEl trabajo reforzó el cuidado necesario para evolucionar sistemas sensibles sin perder auditabilidad. En lugar de separar seguridad y entrega, traté acceso y trazabilidad como parte del contrato del producto.",
      "description": "Mi experiencia de consultoría en sistemas públicos sensibles.",
      "keywords": [
        "para",
        "contexto",
        "trabajé",
        "sistemas",
        "operación",
        "sistema",
        "gestión",
        "salud",
        "cómo",
        "backend"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-eds-policia-civil-rio",
        "slug": "eds-policia-civil-rio",
        "title": "EDS y Policía Civil de Río de Janeiro | Rafael Pereira",
        "description": "Mi experiencia de consultoría en sistemas públicos sensibles.",
        "company": "EDS (Policía Civil de Río de Janeiro)",
        "role": "Ingeniero Backend — Consultoría",
        "period": "jul/2025 a dic/2025",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/eds-policia-civil-rio-es.md"
      }
    },
    {
      "id": "0189ffe3a86deb74",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 5)",
      "content": "Fan-out de workers quebra ordem global. Em telemetria, o requisito costuma ser ordem por dispositivo. Particione por uma chave estável, como `hash(device_id) % N`, e processe cada partição de forma sequencial. Guarde `observed_at`, `ingested_at`, `sequence`, `event_id` e, quando existir, `boot_id`. O relógio do dispositivo pode estar errado ou reiniciar.\n\nProjete a cadeia para entrega *at least once*. Um fluxo seguro recebe o evento, valida envelope e versão, verifica a chave de idempotência, grava efeito e marcador de deduplicação na mesma transação quando possível e só então confirma a mensagem. Para publicação após uma atualização de banco, uma transactional outbox elimina a janela entre confirmar a transação e publicar o evento. Duplicatas ainda podem ocorrer, portanto o consumidor continua idempotente.\n\nRetry serve para falha transitória. Payload inválido e regra de negócio rejeitada não melhoram com novas tentativas. Use limite, budget total e jitter para evitar que todos os pods repitam juntos:\n\n```go\nfunc retry(ctx context.Context, max int, fn func(context.Context) error) error {\n\tvar err error\n\tfor attempt := 0; attempt < max; attempt++ {\n\t\tif err = fn(ctx); err == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tbase := min(100*time.Millisecond<<attempt, 5*time.Second)\n\t\twait := time.Duration(rand.Int64N(int64(base) + 1))\n\t\ttimer := time.NewTimer(wait)\n\t\tselect {\n\t\tcase <-timer.C:\n\t\tcase <-ctx.Done():\n\t\t\tif !timer.Stop() {\n\t\t\t\tselect {\n\t\t\t\tcase <-timer.C:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn context.Cause(ctx)\n\t\t}\n\t}\n\treturn fmt.Errorf(\"retry exhausted: %w\", err)\n}\n```\n\nNa borda, imponha TLS, identidade individual por dispositivo, autorização por tópico, limites de payload e validação antes de alocar estruturas grandes. Rotação, revogação, sequência, nonce e janela temporal entram quando o protocolo de negócio precisa resistir a replay. Não registre credenciais ou payloads sensíveis sem critério.\n\n## Kubernetes, shutdown e observabilidade",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "01cff62c35fd2fc2",
      "url": "https://imrafaeldev.site/projects/goc-mcp-en",
      "title": "goc_mcp",
      "content": "## Problem\n\nA maestro (Codex or Cursor) needs to delegate work to OpenCode workers with an explicit lifecycle: plan, start, follow, respond, cancel, and recover, without infinite agent recursion.\n\n## Constraints\n\nEverything is local. The daemon authenticates on loopback. There are global and per-workspace limits. Interrupted tasks must be reconciled. State corruption must not become blind writes. On the happy path: one OpenCode worker at a time, without automatic decomposition that leaves the maestro without control.\n\n## Decision\n\nGo implementation: MCP gateways over stdio, single daemon, state machine, and FIFO executor. SQLite persistence with WAL; JSONL artifacts. OpenCode adapter with server as the primary path and CLI as fallback. Isolation against recursive delegation and read-only diagnostics if state corrupts.\n\n## Current state\n\nRepository with tests, ADRs, and benchmarks. Orchestration worked. The measurements published in the project itself did not confirm the hypothesis of reducing time and cost.\n\n## Limitations\n\nIt is an experiment. It does not claim agent-engineering productivity gains in production. The negative result of the hypothesis is part of the artifact.",
      "description": "Local agent orchestration over MCP in Go: maestro, FIFO daemon, OpenCode workers, and SQLite. Delivery worked, but the cost and time hypothesis was not confirmed in this measurement.",
      "keywords": [
        "with",
        "state",
        "opencode",
        "without",
        "maestro",
        "daemon",
        "must",
        "path",
        "time",
        "hypothesis"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "projeto-goc-mcp",
        "slug": "goc-mcp",
        "title": "goc_mcp",
        "description": "Local agent orchestration over MCP in Go: maestro, FIFO daemon, OpenCode workers, and SQLite. Delivery worked, but the cost and time hypothesis was not confirmed in this measurement.",
        "excerpt": "Maestro (Codex/Cursor) delegates over MCP; FIFO daemon and OpenCode workers execute with state in SQLite. Orchestration worked, but measurement did not confirm cost or time reduction.",
        "repoUrl": "https://github.com/imrafaeldev/goc_mcp",
        "status": "Documented experiment",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/goc-mcp-en.md"
      }
    },
    {
      "id": "0239984d0f930790",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 8)",
      "content": "**Como investigar latência?** Separe fila, processamento e dependências. Compare p95/p99, lag e saturação; depois use tracing, `pprof` e `go tool trace` para testar uma hipótese.\n\n## Checklist de produção\n\n- Quem é dono de cada goroutine e como ela termina?\n- Onde o contexto propaga cancelamento e deadline?\n- Qual é o limite de concorrência e o comportamento na saturação?\n- A ordem necessária é por chave ou realmente global?\n- Quando ocorre ACK e como a mutação resiste a reentrega?\n- Quais erros vão para retry, DLQ ou quarentena?\n- O que diferencia horário do dispositivo de horário de ingestão?\n- Como TLS, identidade e autorização impedem publicação indevida?\n- O shutdown para de aceitar trabalho antes de encerrar o processo?\n- Quais métricas mostram lag, p99, heap, GC e contenção?\n\n## Referências oficiais\n\n- [Go 1.26 Release Notes](https://go.dev/doc/go1.26)\n- [The Go Memory Model](https://go.dev/ref/mem)\n- [Go Concurrency Patterns: Context](https://go.dev/blog/context)\n- [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines)\n- [Package errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup)\n- [Data Race Detector](https://go.dev/doc/articles/race_detector)\n- [Diagnostics and profiling](https://go.dev/doc/diagnostics)\n- [Package log/slog](https://pkg.go.dev/log/slog)\n- [Kubernetes probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 7,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "036d94ed600c12e7",
      "url": "https://imrafaeldev.site/projetos/md2cv",
      "title": "md2cv (Part 1)",
      "content": "- [Início](/)\n- [Projetos](/projetos/)\n- md2cv\n\nProduto próprio, desktop\n\n## md2cv\n\nPerfil e versões imutáveis ficam no SQLite da máquina. O agente supervisionado só propõe alterações sob schema; ATS e exportação em PDF/DOCX reutilizam o mesmo grafo, sem backend SaaS dono dos dados.\n\n[Repositório](https://github.com/imrafaeldev/md2cv)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\n- [01Problema](#problema)\n- [02Restrições](#restricoes)\n- [03Decisão](#decisao)\n- [04Estado atual](#estado-atual)\n- [05Limitações](#limitacoes)\n\n## Problema\n\nPerfis profissionais espalham-se entre docs, LinkedIn e exports. Cada candidatura pede um ângulo diferente. Adaptar o currículo com um LLM sem fronteira inventa experiência e apaga o contexto da versão anterior.\n\nÉ preciso um grafo versionado na máquina que pergunte o que falta e rejeite saída incompleta, sem prometer contratação ou aprovação automática por ATS.\n\n## Restrições\n\nDesktop e local-first (Electron): sem conta proprietária e sem backend dono dos dados.\n\n- IPC tipado e validado entre renderer e processo principal.\n\n- SQLite com foreign keys, WAL, migrations com checksum, verificações de integridade e backup antes de operação destrutiva.\n\n- Agentes (Codex, Cursor, OpenCode) entram por adaptadores isolados, não como donos do banco.\n\n- Adaptação a candidatura não grava no perfil sem confirmação explícita.\n\n- Acesso externo só sob ação do usuário (pesquisa de URL de empresa ou CLI de IA já configurada no computador); o produto não armazena credenciais de provedores.\n\n## Decisão\n\nRenderer React/Vite separado do processo principal. O produto organiza o trabalho em um único grafo local:\n\n- Perfil: experiências, formação, cursos, idiomas, projetos, links, habilidades e empresas por pessoa.\n\n- Currículos: Markdown, versões imutáveis, restauração rastreável e panorama de evolução.",
      "description": "Estúdio desktop local-first para perfil profissional, currículos Markdown, versões imutáveis, ATS e adaptação a vagas com agentes supervisionados. Os dados ficam no SQLite da máquina.",
      "keywords": [
        "grafo",
        "não",
        "perfil",
        "produto",
        "agente",
        "só",
        "candidatura",
        "currículo",
        "versão",
        "projetos"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/projetos/md2cv"
      }
    },
    {
      "id": "0466392d4458a8a0",
      "url": "https://imrafaeldev.site/es/articulos/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 4)",
      "content": "// core/protocols/CreateUserProtocol.ts\nexport abstract class CreateUserProtocol {\nabstract register(name: string): Promise&#x3C;UserEntity>;\n}\n// core/protocols/GetUserByNameProtocol.ts\nexport abstract class GetUserByNameProtocol {\nabstract getByName(name: string): Promise&#x3C;UserEntity | null>;\n}\nEl Core es el centro; la capa de adaptación conecta el resto.\n\n## Adapter\n\nLos adapters controlan el tráfico bidireccional: externo → regla y regla → externo. Adaptan objetos, parámetros y excepciones — el mismo espíritu del [patrón Adapter](/es/articulos/design-patterns-adapter/).\n\nDos grupos:\n\n- Llamados por el Core — implementan al menos un protocol.\n\n- Llamados por la Infra — en general **services**.\n\n### Connectors, handlers y repositories\n\nClases que implementan protocols. Cada una adapta **un** dispositivo externo (ORM, cliente HTTP, cola, filesystem).\n\nConvención de nombres:\n\n- **Repositories** — protocol ligado a base (vocabulario familiar).\n\n- **Connectors** — retornan datos sin ser “tabla” (ej.: ClientHttpFetchConnector, ClientHttpAxiosConnector).\n\n- **Handlers** — procesan sin retorno síncrono (ej.: publicar en Kafka).\n\nOtros nombres son válidos; el criterio es un adapter por dispositivo.\n\nEn el CRUD, solo repository (mock):\n\n// adapters/repositories/UsersMockRepository.ts\nexport class UsersMockRepository\nimplements\nGetUserByIdProtocol,\nGetUserByNameProtocol,\nCreateUserProtocol,\nUpdateUserProtocol,\nDeleteUserProtocol\n{\nprivate db: DbConnector;\n\nconstructor() {\nthis.db = mockDbConnector;\n}\n\nasync getById(id: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.getById(id);\n}\n\nasync getByName(name: string): Promise&#x3C;UserEntity | null> {\nreturn this.db.users.getByName(name);\n}\n\nasync register(name: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.register(name);\n}\n\nasync update(id: string, name: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.update(id, name);\n}",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "regla",
        "export",
        "architecture",
        "para",
        "class"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/typescript-clean-architecture"
      }
    },
    {
      "id": "04ea8a7c2fa58c82",
      "url": "https://imrafaeldev.site/en/experience/azify",
      "title": "Azify",
      "content": "- [Home](/en/)\n- Azify\n\n## Azify\n\nMy consulting work at Azify with settlement, BaaS, and financial services.\n\nRole\n\nSenior Backend Engineer — Consulting\n\nPeriod\n\nMar 2025 to Jun 2025\n\n## Context\n\nAt Azify, I worked as a consultant on financial infrastructure for fintechs and smaller banks. The product covered capabilities such as Pix, transfers, cards, digital wallets, and other banking services.\n\n## How I worked\n\nI contributed to architectural decisions and helped establish engineering practices for an environment where consistency, security, and stability had direct financial impact. I built a NestJS settlement engine with multi-exchange integrations, risk monitoring, and compliance controls.\n\nI also worked on exchange and blockchain integrations for transactional flows and on a multi-tenant BaaS platform using OAuth 2.0, JWT, and encryption. Through query profiling, index review, and Redis optimization, I reduced latency in critical financial APIs by 30%.\n\n## What I took from it\n\nThis consulting work brought recurring parts of my trajectory, including payments, multi-tenancy, and transactional systems, into a setting with even greater responsibility for authorization and consistency.",
      "description": "My consulting work at Azify with settlement, BaaS, and financial services.",
      "keywords": [
        "azify",
        "financial",
        "consulting",
        "with",
        "worked",
        "2025",
        "work",
        "settlement",
        "baas",
        "services"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/azify"
      }
    },
    {
      "id": "0534f52407fc5f39",
      "url": "https://imrafaeldev.site/cases/vbet-analytics-es",
      "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar (Part 1)",
      "content": "## Contexto\n\nEn VBET, entre octubre de 2023 y febrero de 2025, el producto de analítica servía a influencers y afiliados de iGaming. El dashboard reunía decenas de métricas; la comisión era la lectura más crítica. Los influencers aceptaban un pequeño desfase en los datos del día, siempre que la pantalla respondiera. El pago dependía de datos consolidados del día anterior, no del valor en vivo.\n\n## Restricciones\n\nEl SQL Server era externo, compartido y no modificable de forma fiable. Los índices temporales podían ser eliminados por el propietario de la base. La API original mezclaba consultas SQL construidas a partir de parámetros, con riesgo de inyección, y agregaba demasiado en memoria.\n\n## Problema\n\nEl sistema había sido dimensionado para influencers más pequeños. Con bases mayores, el peor pico del dashboard llegó a cerca de siete minutos. Seguridad y mantenibilidad vinieron antes del rendimiento: queries crudas, poca cobertura de pruebas y un camino síncrono que recalculaba demasiado en cada request.\n\n## Decisión\n\nLa evolución fue incremental, en el orden en que aparecieron las restricciones:\n\n1. eliminar SQL inseguro, parametrizar el acceso, documentar y probar;\n2. optimizar queries e índices temporales, como mitigación y no como invariante;\n3. paralelizar consultas independientes con Go, goroutines y channels;\n4. cuando el cuello de botella volvió al SQL Server, crear ETL y PostgreSQL propios, con precálculo, checkpoints y reconciliación;\n5. separar lectura REALTIME (tendencia, consistencia eventual) de CLOSED (precisión financiera y pago);\n6. cache-aside con TTL alineado al desfase aceptado de cerca de cinco minutos;\n7. degradación controlada si fallaba la caché, en lugar de tumbar la pantalla.\n\n## Alternativa descartada\n\nInsistir en índices en la base externa como arquitectura, o recalcular años de historial en cada acceso. También se descartó la idea de pagar al afiliado con el dato REALTIME.\n\n## Resultado",
      "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
      "keywords": [
        "cerca",
        "índices",
        "minutos",
        "realtime",
        "influencers",
        "dashboard",
        "comisión",
        "pago",
        "base",
        "queries"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "vbet-analytics",
        "slug": "analitica-sql-server-externo",
        "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar",
        "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
        "company": "VBET",
        "role": "Ingeniero Backend Sénior",
        "period": "oct/2023 – feb/2025",
        "excerpt": "La base era de otro equipo. El dashboard necesitaba dejar de depender de un schema que no controlábamos.",
        "proofValue": "~7 min → <1 s",
        "proofLabel": "comisiones, de la carga original a la caché caliente",
        "featuredClaimId": "vbet-commission-performance",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/vbet-analytics-es.md"
      }
    },
    {
      "id": "05de889a55c231ae",
      "url": "https://imrafaeldev.site/en/projects/diffvision",
      "title": "DiffVision",
      "content": "- [Home](/en/)\n- [Projects](/en/projects/)\n- DiffVision\n\nPublic CLI; AI review still mock\n\n## DiffVision\n\nThe Git diff opens in the local UI; comments and Markdown export stay in the repository. Visual AI review remains a mock.\n\n[Repository](https://github.com/imrafaeldev/diffvision-app)\n\nRevisão local-first\nDiff no disco, UI local, comentários e export em `.diffvision/`. A plataforma remota fica de fora; a revisão por IA visual ainda é mock.\n\n- [01Problem](#problem)\n- [02Constraints](#constraints)\n- [03Decision](#decision)\n- [04Current state](#current-state)\n- [05Limitations](#limitations)\n\n## Problem\n\nReviewing a Git diff in a SaaS tool sends code away and mixes remote UI with local history. Review needs to work offline, with hunks, filters, bookmarks, and line-anchored comments.\n\n## Constraints\n\nPreferences and reports must live in the repository itself (.diffvision/). The npm CLI starts local backend and UI. Assistant integration cannot be sold as ready while it is still a prototype.\n\n## Decision\n\nCLI inspects Git, parses unified diff, and serves a web interface. Fastify backend with snapshot and WebSocket; React/Vite UI. Markdown/JSON export in the repository. diffvision-mcp package over stdio to summarize the repository, read patches, and record comments. The visual AI review assistant is declared mock/prototype; comment writing over MCP works.\n\n## Current state\n\nDistributed as an npm CLI, running local-first.\n\n## Limitations\n\nIt does not replace the GitHub review flow. The visual AI flow must not be read as a finished product.",
      "description": "Local-first npm CLI for reviewing Git diffs, with local UI, in-repo comments, Markdown/JSON export, and MCP server. Visual AI review remains a mock.",
      "keywords": [
        "review",
        "repository",
        "diffvision",
        "mock",
        "diff",
        "local",
        "visual",
        "comments",
        "export",
        "with"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/projects/diffvision"
      }
    },
    {
      "id": "063b1ef9995ff108",
      "url": "https://imrafaeldev.site/es/articulos/design-patterns-adapter",
      "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter\n\n## Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter\n\nMigrar de MySQL a PostgreSQL no exige reescribir el servicio. El Adapter aísla el plugin tras un contrato que la regla de negocio entiende.\n\n26 de abril de 2022\n\n- [Arquitectura](/es/articulos/?topic=arquitetura)\n\nCon el patrón Adapter, aislamos la regla de negocio de la dependencia concreta.\n\n## Escenario inicial\n\nTenemos un backend con CRUD simple de usuario: crear, editar, recuperar y eliminar por endpoints en la API. Los datos van a una base cualquiera — digamos MySQL — y la estructura nace como **Controller → Service → Database**. Hasta aquí, el equipo está cómodo.\n\nHasta que alguien decide: la semana que viene migramos de MySQL a PostgreSQL. A partir de ahí la casa se cae para tecnología. Además de reestructurar la base, el equipo necesita cazar referencias a MySQL: inserciones, conexión, queries esparcidas.\n\nLa mayoría de las veces esto retrasa la entrega, reduce la calidad, salta pruebas e introduce bugs. Sería mejor disminuir la dependencia entre la regla de negocio y quien ejecuta la operación específica — en este caso, la base. El Adapter ayuda a construir el sistema así.\n\n## Patrón Adapter\n\nTenemos un plugin, library, module o servicio de terceros que hace algo que queremos en la regla de negocio — aquí, persistir datos. El camino:\n\n- Definir una interfaz con el contrato de lo que necesitamos.\n\n- Exponer solo métodos que tengan sentido en el contexto (SOLID).\n\n- Implementar la interfaz en clases que adaptan el código de terceros.\n\nCreamos CreateDatabaseCustomerProtocol con un método create que recibe CustomerInputEntity y retorna SuccessfulEntityCreation:\n\ninterface CustomerInputEntity {\nname: string;\nemail: string;\nbirthDate: Date;\n}",
      "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
      "keywords": [
        "adapter",
        "regla",
        "negocio",
        "servicio",
        "contrato",
        "mysql",
        "plugin",
        "base",
        "createdatabasecustomerprotocol",
        "readonly"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/design-patterns-adapter"
      }
    },
    {
      "id": "06484dd109e2b091",
      "url": "https://imrafaeldev.site/visual-projects/gran-goias-en",
      "title": "Gran Goiás",
      "content": "Institutional site for Gran Goiás, built around stone execution for large-scale works.",
      "description": "Institutional site for Gran Goiás, with stone execution for large-scale works.",
      "keywords": [
        "institutional",
        "site",
        "gran",
        "goiás",
        "built",
        "around",
        "stone",
        "execution",
        "large-scale",
        "works"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "visual-gran-goias",
        "slug": "gran-goias",
        "title": "Gran Goiás",
        "description": "Institutional site for Gran Goiás, with stone execution for large-scale works.",
        "excerpt": "Institutional site for Gran Goiás, a marble workshop with stone execution for large-scale works.",
        "siteUrl": "https://gran-goias.vercel.app/",
        "imageUrl": "https://gran-goias.vercel.app/_astro/lavatorio-duplo-iluminado.DyCNyxPd_Z4lorq.webp",
        "imageAlt": "Double illuminated washbasin in light veined stone.",
        "imageWidth": "1448",
        "imageHeight": "1086",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "visual-projects/gran-goias-en.md"
      }
    },
    {
      "id": "065e1e058dd56c9d",
      "url": "https://imrafaeldev.site/artigos/intensivao-go",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 4)",
      "content": "return g.Wait()\n}\nClassifique o erro antes de decidir o que fazer. Um banco indisponível pode justificar cancelar o lote. Um payload inválido, duplicado ou fora do schema deve ir para quarentena ou DLQ, sem derrubar o consumer inteiro.\n\nEscolha a primitiva pela propriedade que precisa preservar:\n\nNecessidade\n\nPrimitiva\n\nContador ou flag independente\n\natomic tipado\n\nInvariante entre vários campos\n\nsync.Mutex\n\nLeitura frequente e escrita curta\n\nsync.RWMutex, depois de medir\n\nTransferir trabalho ou ownership\n\nchannel\n\nInicialização única\n\nsync.Once\n\nEsperar tarefas sem erro\n\nsync.WaitGroup\n\nEsperar tarefas com erro e cancelamento\n\nerrgroup\n\nNão copie mutex depois do primeiro uso e não mantenha lock durante I/O remoto. RWMutex não é uma melhoria automática para uma seção crítica pequena.\n\nBackpressure é uma decisão de produto e operação. Se a ingestão recebe 50 mil mensagens por segundo e a persistência confirma 20 mil, acumular o restante em memória apenas muda o incidente de lugar.\n\nPolítica\n\nConsequência\n\nBloquear produtor\n\nAumenta latência e preserva dados quando o protocolo aceita desacelerar\n\nRejeitar com erro\n\nExige retry e idempotência no cliente\n\nPausar consumo ou ACK\n\nMantém backlog no broker durável\n\nDescartar dados antigos\n\nPreserva frescor quando histórico não importa\n\nAgregar ou downsample\n\nReduz resolução para aliviar a carga\n\nPersistir em disco\n\nEvita perda, com custo operacional adicional\n\nDefina tamanho de buffer, métrica de ocupação, timeout e ação de saturação. Buffer sem política não é estratégia de capacidade.\n\n## Pipeline IoT que tolera reentrega\n\nUma separação comum é:\n\ndispositivo -> MQTT/broker -> ingestão Go -> stream -> processadores -> armazenamento\n\\-> DLQ \\-> estado atual\nMQTT atende bem à borda e às conexões dos dispositivos. Um stream como Kafka atende retenção, replay e particionamento interno. gRPC é RPC interno tipado; WebSocket atende atualização de dashboards. Nenhum deles substitui os outros automaticamente.",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "reading",
        "não",
        "para",
        "return",
        "cancelamento",
        "value",
        "quando",
        "context",
        "concorrência",
        "errors"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-go"
      }
    },
    {
      "id": "0661047caa3884c3",
      "url": "https://imrafaeldev.site/experiencias/braistech",
      "title": "Braistech",
      "content": "- [Início](/)\n- Braistech\n\n## Braistech\n\nMinha experiência na Braistech com produto, microsserviços e criptoativos.\n\nCargo\n\nEngenheiro de Software Full Stack Pleno\n\nPeríodo\n\nnov/2019 a jan/2021\n\n## O contexto\n\nNa Braistech, vivi uma das minhas primeiras experiências de produto em um ambiente pequeno, com pouca gente e responsabilidade distribuída. O domínio envolvia contratos de criptoativos e movimentações financeiras.\n\n## Como atuei\n\nLiderei a estruturação do sistema principal com Node.js e NestJS, participei do desenho de microsserviços para o núcleo do negócio e desenvolvi aplicações em Flutter. Também construí um sistema de contratos e trabalhei com integrações de pagamento relacionadas ao ecossistema da Binance.\n\nParticipei ainda da transição de uma organização baseada em MVC para uma arquitetura mais próxima de Clean Architecture. O objetivo era reduzir acoplamento e facilitar a manutenção de um sistema em crescimento, enquanto eu orientava desenvolvedores juniores nas decisões do código.\n\n## O que levo\n\nEssa etapa consolidou meu interesse por backend e arquitetura. O contato com produto completo também me deu uma visão full stack que segue útil nas conversas com frontend e produto.",
      "description": "Minha experiência na Braistech com produto, microsserviços e criptoativos.",
      "keywords": [
        "braistech",
        "produto",
        "sistema",
        "microsserviços",
        "criptoativos",
        "full",
        "stack",
        "contratos",
        "participei",
        "para"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/braistech"
      }
    },
    {
      "id": "07195e30b8e28796",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 1)",
      "content": "El desarrollo de software cambia todo el tiempo. La arquitectura débil se vuelve mantenimiento caro, feature lenta, prueba difícil y bug difícil de aislar. Vale invertir en una estructura que soporte evolución sin reescribir el sistema ante cada presión del negocio.\n\n## Un poco de historia\n\nClean Architecture es el nombre que Robert C. Martin (Uncle Bob) dio, en 2012, en el libro *Clean Architecture: A Craftsman's Guide to Software Structure and Design*. La propuesta huye de la rigidez de arquitecturas acopladas a framework y base: el núcleo queda estable; los detalles externos cambian. La idea bebe de DDD, SOLID, Onion Architecture y Hexagonal Architecture.\n\n## Propuesta general\n\nEste artículo describe Clean Architecture y una derivación práctica para backends en TypeScript: tres capas — **Core**, **Adapters** e **Infra**.\n\n- **Core** — regla de negocio y entidades del dominio. Capa más interna.\n- **Infra** — conexiones externas: repositorios concretos, controllers REST, módulos de DI, boilerplate de framework.\n- **Adapters** — intermediación en ambos sentidos. El controller no llama al usecase \"crudo\": pasa por un servicio. El usecase no habla con la base: habla con un protocolo que un adapter (repositorio, connector, handler) implementa.\n\nCada capa tiene capacidades y restricciones distintas; SOLID pesa más en el Core. Sirve para CRUD HTTP y para sistemas con varios frameworks y canales.\n\nBeneficios concretos: responsabilidades claras (lectura y mantenimiento), flexibilidad para cambiar plugin sin reescribir regla, y pruebas aisladas por capa.\n\n## Guía de capas\n\nEjemplo: CRUD de usuarios vía REST con NestJS. Detalles de instalación quedan fuera. Escritura **core-to-infra** (de dentro hacia fuera).\n\n## Core\n\nEn el diseño clásico, *domain* y *entities* quedan muy próximas. Aquí forman el **Core**: todo lo que la regla de negocio *es* — funcionalidades y representaciones del dominio.\n\nEn el ejemplo, la entidad principal es Usuario (`id`, `name`), en `core/entities`.",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "07587c43ab661062",
      "url": "https://imrafaeldev.site/cases/vbet-analytics-pt-br",
      "title": "VBET: analytics sobre um SQL Server que não podíamos mudar (Part 1)",
      "content": "## Contexto\n\nNa VBET, entre outubro de 2023 e fevereiro de 2025, o produto de analytics servia influenciadores e afiliados de iGaming. O dashboard reunia dezenas de métricas; comissão era a leitura mais crítica. Influenciadores aceitavam pequena defasagem nos dados do dia, desde que a tela respondesse. Pagamento dependia de dados consolidados do dia anterior, não do valor ao vivo.\n\n## Restrições\n\nO SQL Server era externo, compartilhado e não modificável de forma confiável. Índices temporários podiam ser removidos pelo proprietário da base. A API original misturava consultas SQL montadas a partir de parâmetros, com risco de injeção, e agregava demais em memória.\n\n## Problema\n\nO sistema havia sido dimensionado para influenciadores menores. Com bases maiores, o pior pico do dashboard chegou a cerca de sete minutos. Segurança e manutenibilidade vieram antes da performance: queries cruas, pouca cobertura de testes e um caminho síncrono que recalculava demais a cada request.\n\n## Decisão\n\nA evolução foi incremental, na ordem em que as restrições apareceram:\n\n1. remover SQL inseguro, parametrizar acesso, documentar e testar;\n2. otimizar queries e índices temporários, como mitigação e não como invariante;\n3. paralelizar consultas independentes com Go, goroutines e channels;\n4. quando o gargalo voltou para o SQL Server, criar ETL e PostgreSQL próprios, com pré-cálculo, checkpoints e reconciliação;\n5. separar leitura REALTIME (tendência, consistência eventual) de CLOSED (precisão financeira e pagamento);\n6. cache-aside com TTL alinhado à defasagem aceita de cerca de cinco minutos;\n7. degradação controlada se o cache falhasse, em vez de derrubar a tela.\n\n## Alternativa descartada\n\nInsistir em índices na base externa como arquitetura, ou recalcular anos de histórico a cada acesso. Também descartada a ideia de pagar o afiliado com o dado REALTIME.\n\n## Resultado\n\nA linha reconciliada no dossiê da experiência, para o caminho de comissão/dashboard, é:",
      "description": "Dashboard de comissões na VBET: SQL Server externo, ETL próprio e cache. A linha medida foi de cerca de sete minutos no pico até menos de um segundo com cache quente.",
      "keywords": [
        "cerca",
        "não",
        "índices",
        "minutos",
        "realtime",
        "influenciadores",
        "dashboard",
        "comissão",
        "pagamento",
        "base"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "vbet-analytics",
        "slug": "analytics-sql-server-externo",
        "title": "VBET: analytics sobre um SQL Server que não podíamos mudar",
        "description": "Dashboard de comissões na VBET: SQL Server externo, ETL próprio e cache. A linha medida foi de cerca de sete minutos no pico até menos de um segundo com cache quente.",
        "company": "VBET",
        "role": "Engenheiro Backend Sênior",
        "period": "out/2023 a fev/2025",
        "excerpt": "O banco era de outro time. O dashboard precisava deixar de depender de um schema que não controlávamos.",
        "proofValue": "~7 min → <1 s",
        "proofLabel": "comissões, da carga original ao cache quente",
        "featuredClaimId": "vbet-commission-performance",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/vbet-analytics-pt-br.md"
      }
    },
    {
      "id": "07aa01ec0edaba65",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-en",
      "title": "Design Patterns: Strategy (Part 2)",
      "content": "```typescript\ninterface BinaryOperationParameters {\n  firstOperand: number;\n  secondOperand: number;\n  operator: string;\n}\n\ntype Result = number;\n\ninterface BinaryOperationStrategy {\n  calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result;\n}\n```\n\n## Implementing concrete strategies\n\nEach math operation becomes a class implementing `BinaryOperationStrategy` and executing a single operation. Addition and division:\n\n```typescript\nclass Sum implements BinaryOperationStrategy {\n  public calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result {\n    const { firstOperand, secondOperand } = parameters;\n    return firstOperand + secondOperand;\n  }\n}\n\nclass Division implements BinaryOperationStrategy {\n  public calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result {\n    const { firstOperand, secondOperand } = parameters;\n    if (secondOperand === 0) {\n      throw new Error(\"Division by zero is not allowed!\");\n    }\n    return firstOperand / secondOperand;\n  }\n}\n```\n\n## The Context and the Factory\n\nTo tie strategies together, a Context and a Factory (or Analyzer) come in. In `ContextAnalyzer`, a method evaluates the operator and returns the right Strategy:\n\n```typescript\nclass ContextAnalyzer {\n  public getInstance(operator: string): BinaryOperationStrategy {\n    switch (operator) {\n      case \"*\":\n        return new Multiplication();\n      case \"+\":\n        return new Sum();\n      case \"-\":\n        return new Subtraction();\n      case \"/\":\n        return new Division();\n      case \"%\":\n        return new Percent();\n      case \"**\":\n        return new Pow();\n      default:\n        throw new Error(\"Operator not found!\");\n    }\n  }\n}\n```",
      "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "class",
        "operator",
        "parameters",
        "each",
        "operation"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
        "excerpt": "If/else chains grow and turn fragile. Strategy isolates each algorithm behind a contract.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-en.md"
      }
    },
    {
      "id": "0805622129e245a6",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 5)",
      "content": "La composición final de los indicadores fue a la aplicación. Esa etapa venía después de las relaciones y agregaciones que elevaban el volumen intermedio y mantenían alta la CPU de la base. Con los datos ya recortados, el servidor podía montar los valores de la pantalla sin concentrar toda la ejecución en una única query.\n\nLa base pasó a reducir el conjunto temprano. La aplicación recibía los registros seleccionados y componía los indicadores. Tras el cambio, el dashboard dejó de depender de aquella consulta pesada y quedó más predecible, porque la división acompañaba el punto en que el costo crecía en el plan.\n\nEsa frontera no vino de una preferencia por query única o por más lógica en la aplicación. Marqué dónde CPU, lecturas y filas intermedias se disparaban después de los filtros selectivos. Entonces verifiqué si el tramo caro podría recibir fuera de la base un conjunto ya reducido.\n\nAntes de mover esa etapa, dos cuentas necesitaban cerrar. La base debía seguir haciendo el recorte que aprovechaba los índices. La aplicación debía componer el resultado sin buscar demasiadas filas, crear llamadas por ítem o transformar la red en el nuevo cuello de botella.\n\nLa validación necesitaba mostrar menos CPU y lecturas en la base sin aumentar el payload ni la latencia de la ruta. Sin esa mejora, la división solo transferiría el costo a otra capa y el dashboard seguiría caro.\n\n## Cuando el plan apunta fuera de la base\n\nEn otra ruta, el plan mostraba una consulta saludable, con lecturas y tiempo dentro de lo esperado. Aun así, la API seguía lenta. Cuando medí el flujo entero, el retraso apareció después de la consulta, en el procesamiento de la aplicación y en llamadas remotas ejecutadas en secuencia.\n\nUn tramo así cambia la cuenta.\n\n```typescript\nfor (const item of itens) {\n  await buscarDetalhe(item.id);\n}\n```",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "0819e89a61075431",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 4)",
      "content": "I also do not take the plan's displayed cost percentage as verdict. It serves to choose where to measure. I mark the expensive operator, how many rows enter and leave it, and how much time it consumes.\n\nIf cost shows up after row multiplication and before the few needed indicators, there is already a concrete hypothesis to test.\n\n## Split the query where cost grows\n\nWhat fixed the dashboard was splitting the query and using indexes properly. The split did not happen arbitrarily. The plan itself showed where cost started growing.\n\nWe kept in the database the filters and indexed lookup of the needed records. Moving that slice to the application would make the server receive a larger set before it could discard it. It would also waste the path indexes already shortened.\n\nFinal indicator composition moved to the application. That stage came after the relationships and aggregations that raised intermediate volume and kept database CPU high. With data already sliced, the server could assemble screen values without concentrating all execution in a single query.\n\nThe database started reducing the set early. The application received the selected records and composed the indicators. After the change, the dashboard stopped depending on that heavy query and became more predictable, because the split followed the point where cost grew in the plan.\n\nThat boundary did not come from a preference for single queries or for more application logic. I marked where CPU, reads, and intermediate rows spiked after selective filters. Then I checked whether the expensive stretch could receive an already-reduced set outside the database.\n\nBefore moving that stage, two bills had to balance. The database should keep doing the slicing that used the indexes. The application should compose the result without fetching too many rows, creating per-item calls, or turning the network into the new bottleneck.",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "08ee566dc771f7be",
      "url": "https://imrafaeldev.site/casos/analytics-sql-server-externo",
      "title": "VBET: analytics sobre um SQL Server que não podíamos mudar (Part 1)",
      "content": "- [Início](/)\n- [Casos](/casos/)\n- VBET: analytics sobre um SQL Server que não podíamos mudar\n\nVBET\n\n## VBET: analytics sobre um SQL Server que não podíamos mudar\n\nO banco era de outro time. O dashboard precisava deixar de depender de um schema que não controlávamos.\n\nEngenheiro Backend Sênior out/2023 a fev/2025\n\n~7 min → <1 s comissões, da carga original ao cache quente\n\nPipeline de comissões\nCada estágio corresponde a uma decisão incremental documentada no case. Números de outras histórias não entram neste desenho.\n\n- [01Contexto](#contexto)\n- [02Restrições](#restricoes)\n- [03Problema](#problema)\n- [04Decisão](#decisao)\n- [05Alternativa descartada](#alternativa-descartada)\n- [06Resultado](#resultado)\n- [07Limitações](#limitacoes)\n\n## Contexto\n\nNa VBET, entre outubro de 2023 e fevereiro de 2025, o produto de analytics servia influenciadores e afiliados de iGaming. O dashboard reunia dezenas de métricas; comissão era a leitura mais crítica. Influenciadores aceitavam pequena defasagem nos dados do dia, desde que a tela respondesse. Pagamento dependia de dados consolidados do dia anterior, não do valor ao vivo.\n\n## Restrições\n\nO SQL Server era externo, compartilhado e não modificável de forma confiável. Índices temporários podiam ser removidos pelo proprietário da base. A API original misturava consultas SQL montadas a partir de parâmetros, com risco de injeção, e agregava demais em memória.\n\n## Problema\n\nO sistema havia sido dimensionado para influenciadores menores. Com bases maiores, o pior pico do dashboard chegou a cerca de sete minutos. Segurança e manutenibilidade vieram antes da performance: queries cruas, pouca cobertura de testes e um caminho síncrono que recalculava demais a cada request.\n\n## Decisão\n\nA evolução foi incremental, na ordem em que as restrições apareceram:\n\n- remover SQL inseguro, parametrizar acesso, documentar e testar;\n\n- otimizar queries e índices temporários, como mitigação e não como invariante;",
      "description": "Dashboard de comissões na VBET: SQL Server externo, ETL próprio e cache. A linha medida foi de cerca de sete minutos no pico até menos de um segundo com cache quente.",
      "keywords": [
        "não",
        "cerca",
        "imrafaeldev",
        "vbet",
        "server",
        "dashboard",
        "cache",
        "cada",
        "índices",
        "para"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/casos/analytics-sql-server-externo"
      }
    },
    {
      "id": "091ca3a884392d92",
      "url": "https://imrafaeldev.site/es/articulos/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 3)",
      "content": "/**\n* La implementación de calculate en Calculator no cambia por operación.\n* Lo que crece es el ContextAnalyzer, que añade un case por operación nueva.\n*/\npublic calculate(parameters: BinaryOperationParameters): Result {\nconst { operator, firstOperand, secondOperand } = parameters;\nreturn this.contextAnalyzer\n.getInstance(operator)\n.calculate({ firstOperand, secondOperand });\n}\n}\n\n## ¿Por qué usar Strategy?\n\nStrategy ayuda en legado con varias reglas de negocio, cada una representada por un if y una implementación extensa. La parte común queda en el contrato; cada variación de regla queda en su propia clase; un analizador de contexto (resolver/factory) elige la estrategia.\n\nEn cada petición, el código evalúa el contexto de la operación y selecciona la implementación correspondiente al contrato.\n\n## Relación con otros patrones\n\n- Adapter: Strategy varía comportamiento; Adapter aísla dependencias externas tras una interfaz propia.\n\n- SOLID (OCP): Strategy es una forma de aplicar el Open/Closed Principle.\n\nStrategy: contrato, seleção e concretas\nCalculator depende do contrato BinaryOperationStrategy. ContextAnalyzer escolhe a concreta (ex.: Sum) pelo operador; Sum, Division e Pow implementam o mesmo contrato.",
      "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "contrato",
        "parameters",
        "operator",
        "cada",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/design-patterns-strategy"
      }
    },
    {
      "id": "0a241f6a5c2b9851",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-pt-br",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 1)",
      "content": "A tese é simples: Node.js com Event Loop e Go com goroutines não resolvem o mesmo tipo de problema do mesmo jeito. A comparação fica ruim quando a gente trata os dois como concorrentes diretos em qualquer cenário. Na prática, o erro mais comum é confundir concorrência com paralelismo.\n\nConcorrência é organizar várias tarefas que podem estar em andamento ao mesmo tempo. Paralelismo é executar trabalho de fato ao mesmo tempo, usando múltiplos núcleos de CPU. Essa diferença parece acadêmica até aparecer em produção.\n\nO Event Loop do Node.js é muito bom quando o gargalo está em espera: API externa, banco de dados, WebSocket, input de usuário, filas e eventos. Enquanto uma operação aguarda resposta, o loop continua atendendo outras tarefas. É o dono da bodega no balcão: ele não para porque pediu a alguém para buscar a rapadura no estoque.\n\nGoroutines, por outro lado, começam a ficar mais interessantes quando o trabalho é CPU bound, divisível e pode aproveitar múltiplos núcleos com controle explícito de concorrência. Elas são unidades leves de execução gerenciadas pelo runtime do Go. Com elas, dá para quebrar uma tarefa em partes menores, distribuir a execução e sincronizar o resultado no final.\n\nÉ mais parecido com uma barraca cheia no São João de Caruaru: uma pessoa assa o milho, outra mexe a canjica, outra corta o bolo de rolo. O trabalho avança ao mesmo tempo, com cada pessoa cuidando de uma parte.\n\n## O ponto onde o Node.js começa a sofrer\n\nEu vi isso de forma bem concreta em um processo de cálculo de comissão em uma casa de apostas. A aplicação lidava com milhões de apostas por dia, e parte do fluxo envolvia calcular comissão sobre vários lotes de apostas.\n\nNo começo, o processo em Node.js funcionava. Sequencialmente era correto, mas lento. Quando tentei paralelizar com a lógica comum de várias tarefas ao mesmo tempo, o limite apareceu: o gargalo era CPU.\n\nNão era só esperar banco, API ou evento externo. Era cálculo em cima de um buffer grande de apostas.",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "mais",
        "mesmo",
        "tempo",
        "trabalho",
        "loop",
        "problema",
        "pode"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência",
        "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
        "excerpt": "Node.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-pt-br.md"
      }
    },
    {
      "id": "0c4d8c2b6eee60f6",
      "url": "https://imrafaeldev.site/experiences/infosistemas-pt-br",
      "title": "Infosistemas | Rafael Pereira",
      "content": "## O contexto\n\nNa Infosistemas, atuei em plataformas para locadoras, frotas, montadoras e operações de mobilidade. Era um ambiente enterprise, com integrações, fluxos fiscais e serviços de grande volume, no qual trabalhei próximo de DevOps, SREs e DBAs.\n\n## Como atuei\n\nMinha atuação combinou arquitetura e execução hands-on. Redesignei fluxos entre microsserviços, liderei integrações em NestJS e Go e implementei rastreabilidade de eventos críticos com NestJS e MongoDB. Também evoluí APIs, investiguei problemas de segurança e participei de jornadas digitais e de componentes do webapp quando a continuidade entre backend e interface era necessária.\n\nO ponto que mais orientou meu trabalho foi tornar falhas observáveis e tratáveis desde o desenho. Na mensageria, tratei durabilidade, retry, idempotência e controle de consumo como partes do fluxo, não como correções posteriores.\n\n## O que levo\n\nEssa experiência ampliou minha atuação em sistemas com muitas dependências e especialistas envolvidos. Aprendi a transformar requisitos, riscos e restrições operacionais em decisões que continuassem claras até a validação com o cliente.",
      "description": "Minha experiência na Infosistemas com mensageria, integrações e plataformas de mobilidade.",
      "keywords": [
        "como",
        "atuei",
        "integrações",
        "fluxos",
        "minha",
        "atuação",
        "entre",
        "nestjs",
        "contexto",
        "infosistemas"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-infosistemas",
        "slug": "infosistemas",
        "title": "Infosistemas | Rafael Pereira",
        "description": "Minha experiência na Infosistemas com mensageria, integrações e plataformas de mobilidade.",
        "company": "Infosistemas",
        "role": "Engenheiro de Software Sênior / Arquiteto de Software",
        "period": "fev/2025 a mai/2026",
        "caseSlug": "mensageria-rabbitmq",
        "caseSummary": "O case detalha como redesenhei a mensageria RabbitMQ para tornar fluxos críticos mais previsíveis e reduzir falhas intermitentes entre microsserviços.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/infosistemas-pt-br.md"
      }
    },
    {
      "id": "0d6caeb28bca5630",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-es",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 5)",
      "content": "Después de ese caso, la regla que me quedó es esta: si el problema es esperar muchas cosas al mismo tiempo, Node.js con Event Loop tiende a orquestar muy bien. Si el problema es calcular muchas cosas al mismo tiempo, considero Go con goroutines más temprano.\n\nPero también pasé a desconfiar de la migración que promete resolverlo todo. Cambiar tecnología puede solo desplazar el cuello de botella.\n\nEn mi caso, mejoró CPU y tiempo, pero obligó a reanalizar memoria y chunking.\n\nAl final, la pelea entre goroutines y Event Loop es menos sobre qué modelo es superior y más sobre qué cuello de botella intentas atacar.\n\nPara I/O, el mostrador de la bodega funciona muy bien. Para CPU, a veces necesitas más gente en la cocina de la fiesta.\n\n---\n\n*Publicado originalmente en [LinkedIn](https://www.linkedin.com/pulse/goroutines-vs-event-loop-compara%C3%A7%C3%A3o-errada-entre-dois-s-pereira-qtgle/) el 24 de junio de 2026.*",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "más",
        "tiempo",
        "mismo",
        "trabajo",
        "loop",
        "problema",
        "para",
        "resultado"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia",
        "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
        "excerpt": "Node.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-es.md"
      }
    },
    {
      "id": "0d6cd6d132325c8f",
      "url": "https://imrafaeldev.site/artigos/typescript-cleanarch",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 5)",
      "content": "export const mockDbConnector: DbConnector = {\nusers: {\ngetById: async (id: string) =>\nPromise.resolve(new UserEntity({ id, name: \"Test\" })),\ngetByName: async (name: string) =>\nPromise.resolve(new UserEntity({ id: \"1\", name })),\nregister: async (name: string) =>\nPromise.resolve(new UserEntity({ id: \"2\", name })),\nupdate: async (id: string, name: string) =>\nPromise.resolve(new UserEntity({ id, name })),\ndelete: async (_id: string) => Promise.resolve(),\n},\nprofiles: {\ngetById: async (_id: string) => Promise.resolve(null),\ngetByName: async (_name: string) => Promise.resolve(null),\nregister: async (_name: string) => Promise.resolve(null),\nupdate: async (_id: string, _name: string) => Promise.resolve(null),",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "name",
        "string",
        "core",
        "promise",
        "userentity",
        "async",
        "para",
        "this",
        "regra",
        "export"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/artigos/typescript-cleanarch"
      }
    },
    {
      "id": "0f01ade0f8038d98",
      "url": "https://imrafaeldev.site/artigos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 2)",
      "content": "Eu vi isso de forma bem concreta em um processo de cálculo de comissão em uma casa de apostas. A aplicação lidava com milhões de apostas por dia, e parte do fluxo envolvia calcular comissão sobre vários lotes de apostas.\n\nNo começo, o processo em Node.js funcionava. Sequencialmente era correto, mas lento. Quando tentei paralelizar com a lógica comum de várias tarefas ao mesmo tempo, o limite apareceu: o gargalo era CPU.\n\nNão era só esperar banco, API ou evento externo. Era cálculo em cima de um buffer grande de apostas.\n\nEsse é o tipo de cenário em que Promise.all pode enganar. Ele passa a sensação de paralelismo, mas não transforma automaticamente trabalho pesado de CPU em execução paralela real. Se as tarefas são computação intensa e rodam no mesmo thread principal, o Event Loop fica ocupado.\n\nO resultado pode ser pior do que o esperado: bloqueio do loop, aumento de latência, pior responsividade e maior pressão sobre CPU e memória.\n\nO problema não era Node.js ser ruim. O problema era usar o modelo padrão do Node para uma carga que exigia outro tipo de execução.\n\n## Onde Go entrou melhor\n\nA solução foi reescrever esse processo em Go usando goroutines. A ideia era dividir o cálculo em chunks menores, processar esses pedaços em paralelo e sincronizar apenas no final.\n\nEsse desenho encaixava melhor no problema porque o trabalho era CPU bound e podia ser dividido. Em vez de um fluxo centralizado tentando coordenar várias operações pesadas, o processamento passou a ser distribuído em unidades menores de execução.\n\nCom um worker pool, por exemplo, dá para controlar o número de goroutines, limitar o fan-out, usar melhor os cores disponíveis e evitar que o sistema dispare trabalho sem limite.\n\nO ganho apareceu. O tempo total caiu cerca de 25%. O processo que ficava na casa dos 30 segundos passou a rodar em algo próximo de 22,5 segundos. Também houve melhor uso de CPU.",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "loop",
        "mesmo",
        "trabalho",
        "event",
        "problema",
        "mais",
        "tempo"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/artigos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "0fad6e225c71908a",
      "url": "https://imrafaeldev.site/experiences/eds-policia-civil-rio-pt-br",
      "title": "EDS e Polícia Civil do Rio de Janeiro | Rafael Pereira",
      "content": "## O contexto\n\nNa consultoria para a EDS, trabalhei em sistemas destinados à Polícia Civil do Rio de Janeiro. O contexto envolvia uma operação pública crítica, um sistema de gestão de saúde de alto volume e a evolução de um ERP jurídico.\n\n## Como atuei\n\nEstruturei o backend do sistema de saúde com NestJS e SQL Server. Também refatorei rotas legadas e participei do desenho de fluxos para automação de processos, gestão documental e coleta de evidências. Segurança, controle de acesso, rastreabilidade e LGPD não eram requisitos isolados: orientavam como cada rota precisava evoluir.\n\nAlém do backend, colaborei com a manutenção de componentes compartilhados do design system para alinhar contratos de API e o comportamento das interfaces usadas na operação.\n\n## O que levo\n\nFoi uma experiência que reforçou o cuidado necessário para evoluir sistemas sensíveis sem perder auditabilidade. Em vez de separar segurança da entrega, tratei acesso e rastreabilidade como parte do contrato do produto.",
      "description": "Minha experiência de consultoria em sistemas públicos sensíveis.",
      "keywords": [
        "para",
        "como",
        "contexto",
        "sistemas",
        "operação",
        "sistema",
        "gestão",
        "saúde",
        "backend",
        "segurança"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-eds-policia-civil-rio",
        "slug": "eds-policia-civil-rio",
        "title": "EDS e Polícia Civil do Rio de Janeiro | Rafael Pereira",
        "description": "Minha experiência de consultoria em sistemas públicos sensíveis.",
        "company": "EDS (Polícia Civil do Rio de Janeiro)",
        "role": "Engenheiro Backend — Consultoria",
        "period": "jul/2025 a dez/2025",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/eds-policia-civil-rio-pt-br.md"
      }
    },
    {
      "id": "10b7bb195258c1b9",
      "url": "https://imrafaeldev.site/en/articles/derusting-logic-group-anagrams",
      "title": "Derusting logic #01: Group Anagrams (Part 2)",
      "content": "return result\n}\nWhat happens in this code:\n\n- sortString(str) turns the string into bytes, sorts the characters, and returns a new string.\n\n- sortedStr := sortString(str) produces the key for that term.\n\n- mapping[sortedStr] = append(...) uses that key to accumulate anagrams in the same group.\n\n- For eat, tea, and ate, sortedStr is always aet.\n\nThe map starts looking like this:\n\n\"aet\" -> [\"eat\", \"tea\", \"ate\"]\n\"ant\" -> [\"tan\", \"nat\"]\n\"abt\" -> [\"bat\"]\nI compute one key and add the word directly to the matching group. The map avoids comparing every string with every other.\n\n## Where is the cost of this approach?\n\nTo discover the key, I sort each string. If a string has k characters, that sort costs roughly O(k log k). Repeating for n strings, the dominant part is O(n × k log k).\n\n### Why drop the sort?\n\nFor eat, I sorted characters to reach aet. For tea, I sorted again to reach the same aet. Sorting works because it creates a shared representation. But the problem does not require sorting anything.\n\nTo know whether two strings are anagrams, it is enough to check whether they hold the same count of each letter.\n\n## That observation changes the solution\n\nInstead of placing letters in the same order, I can count how many times each appears. In this exercise, inputs use lowercase letters from a to z. Each string can be represented by 26 counters.\n\ntea and ate produce the same count. I do not need to rearrange any character. I just walk the string and count occurrences. For eat, the relevant part is:\n\na = 1\ne = 1\nt = 1\n\n### Counting solution\n\nvar key [26]uint8 creates 26 slots, one per letter from a to z. key[str[i]-'a']++ finds each character’s slot and increments the counter. Then groups[key] = append(groups[key], str) uses the frequency vector itself as the group key.\n\nfunc groupAnagrams(strs []string) [][]string {\ngroups := make(map[[26]uint8][]string, len(strs))\n\nfor _, str := range strs {\nvar key [26]uint8\n\nfor i := 0; i &#x3C; len(str); i++ {\nkey[str[i]-'a']++\n}",
      "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
      "keywords": [
        "string",
        "group",
        "that",
        "result",
        "anagrams",
        "same",
        "solution",
        "what",
        "each",
        "problem"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/en/articles/derusting-logic-group-anagrams"
      }
    },
    {
      "id": "11a1c9d89d24b559",
      "url": "https://imrafaeldev.site/es/articulos",
      "title": "Artículos (Part 1)",
      "content": "- [Inicio](/es/)\n- Artículos\n\n## Artículos\n\nPatrones y decisiones de ingeniería en prosa técnica, con código.\n\nTodosRendimientoArquitecturaTrade-offsMensajería\n\n-\n\n## [Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos](/es/articulos/go-intensivo/)\n\n22 de septiembre de 2026\n\n[Arquitectura](/es/articulos/?topic=arquitetura)\n- [Rendimiento](/es/articulos/?topic=performance)\n- [Mensajería](/es/articulos/?topic=mensageria)\nUna guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.\n\n-\n\n## [Desenoxidando la lógica #02: Container With Most Water](/es/articulos/desenoxidando-logica-container-with-most-water/)\n\n15 de septiembre de 2026\n\n[Rendimiento](/es/articulos/?topic=performance)\n- [Trade-offs](/es/articulos/?topic=trade-offs)\nIntenté elegir el próximo paso mirando solo a los vecinos. Funcionó en algunos casos, pero el problema pedía una visión más amplia.\n\n-\n\n## [Desenoxidando la lógica #01: Group Anagrams](/es/articulos/desenoxidando-logica-group-anagrams/)\n\n17 de agosto de 2026\n\n[Trade-offs](/es/articulos/?topic=trade-offs)\n- [Rendimiento](/es/articulos/?topic=performance)\nYo no había dejado de escribir código. Lo que cambió fue tercerizar partes del razonamiento. Group Anagrams fue el ejercicio para recuperar el hábito.\n\n-\n\n## [La mayoría de los problemas de performance de backend empieza cerca de los datos](/es/articulos/backend-performance-cerca-de-los-datos/)\n\n16 de julio de 2026\n\n[Rendimiento](/es/articulos/?topic=performance)\n- [Arquitectura](/es/articulos/?topic=arquitetura)\nAPI lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.\n\n-\n\n## [Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia](/es/articulos/goroutines-vs-event-loop/)\n\n24 de junio de 2026",
      "description": "Patrones y decisiones de ingeniería en prosa técnica, con código.",
      "keywords": [
        "articulos",
        "topic",
        "arquitectura",
        "performance",
        "trade-offs",
        "2026",
        "arquitetura",
        "rendimiento",
        "concurrencia",
        "para"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/es/articulos"
      }
    },
    {
      "id": "11ce90cf8f03dad1",
      "url": "https://imrafaeldev.site/projects/md2cv-pt-br",
      "title": "md2cv (Part 1)",
      "content": "## Problema\n\nPerfis profissionais espalham-se entre docs, LinkedIn e exports. Cada candidatura pede um ângulo diferente. Adaptar o currículo com um LLM sem fronteira inventa experiência e apaga o contexto da versão anterior.\n\nÉ preciso um grafo versionado na máquina que pergunte o que falta e rejeite saída incompleta, sem prometer contratação ou aprovação automática por ATS.\n\n## Restrições\n\nDesktop e local-first (Electron): sem conta proprietária e sem backend dono dos dados.\n\n- IPC tipado e validado entre renderer e processo principal.\n- SQLite com foreign keys, WAL, migrations com checksum, verificações de integridade e backup antes de operação destrutiva.\n- Agentes (Codex, Cursor, OpenCode) entram por adaptadores isolados, não como donos do banco.\n- Adaptação a candidatura não grava no perfil sem confirmação explícita.\n- Acesso externo só sob ação do usuário (pesquisa de URL de empresa ou CLI de IA já configurada no computador); o produto não armazena credenciais de provedores.\n\n## Decisão\n\nRenderer React/Vite separado do processo principal. O produto organiza o trabalho em um único grafo local:\n\n- Perfil: experiências, formação, cursos, idiomas, projetos, links, habilidades e empresas por pessoa.\n- Currículos: Markdown, versões imutáveis, restauração rastreável e panorama de evolução.\n- ATS: diagnósticos estruturais e pontuação orientativa; PDF textual marcado e DOCX semântico.\n- Candidaturas: empresa, vaga, currículo base, versão e estado no mesmo contexto.\n- Agentes: máquina de estados para perguntas, tentativas e proposta de nova versão sob schema; só persiste com confirmação.\n- Dados: importação e exportação versionada do grafo completo; compilador Markdown (unified/remark) compartilhado por preview, auditoria e export.\n\nFluxo canônico: perfil → currículo base → versão imutável → auditoria ATS → PDF/DOCX. O ramo de candidatura passa por agente local supervisionado antes do currículo adaptado.\n\n## Estado atual",
      "description": "Estúdio desktop local-first para perfil profissional, currículos Markdown, versões imutáveis, ATS e adaptação a vagas com agentes supervisionados. Os dados ficam no SQLite da máquina.",
      "keywords": [
        "não",
        "currículo",
        "versão",
        "candidatura",
        "grafo",
        "agentes",
        "perfil",
        "produto",
        "entre",
        "experiência"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "projeto-md2cv",
        "slug": "md2cv",
        "title": "md2cv",
        "description": "Estúdio desktop local-first para perfil profissional, currículos Markdown, versões imutáveis, ATS e adaptação a vagas com agentes supervisionados. Os dados ficam no SQLite da máquina.",
        "excerpt": "Perfil e versões imutáveis ficam no SQLite da máquina. O agente supervisionado só propõe alterações sob schema; ATS e exportação em PDF/DOCX reutilizam o mesmo grafo, sem backend SaaS dono dos dados.",
        "repoUrl": "https://github.com/imrafaeldev/md2cv",
        "status": "Produto próprio, desktop",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "projects/md2cv-pt-br.md"
      }
    },
    {
      "id": "13c6af45423d158e",
      "url": "https://imrafaeldev.site/en/articles/go-intensive",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 4)",
      "content": "Treat the end-to-end path as *at least once*. Receive the event, validate its envelope and schema version, check an idempotency key, persist the effect and deduplication marker in one transaction where possible, then ACK. A transactional outbox closes the gap between committing database state and publishing a following event. Consumers still need idempotency because duplicates remain possible.\n\nRetry only transient failures. Invalid payloads and rejected business rules do not improve with another attempt. Add a limit, a total budget, and jitter so replicas do not retry together.\n\nAt the edge, use TLS, per-device identities, topic authorization, strict payload limits, and validation before allocating large structures. Rotation, revocation, sequence numbers, nonces, and time windows matter when the business protocol must resist replay.\n\n## Kubernetes, observability, and performance\n\nOn SIGTERM, remove readiness, stop fetching work, drain in-flight work within the grace period, ACK only completed messages, and close producers, connections, and telemetry last. Liveness asks whether the process progresses; it should not depend on every external service. Readiness asks whether this pod can accept work now.\n\nFor consumer autoscaling, CPU alone is weak. Watch lag, age of the oldest message, arrival rate, processing time, and worker-pool occupancy.\n\nUse structured logs and correlation fields without logging credentials or full sensitive payloads. Track throughput, errors by class, p50/p95/p99, lag, event age, retries, DLQ volume, duplicates, goroutines, heap, and GC pauses. Use sampled traces across ingestion, stream, and persistence; tracing every high-frequency reading can cost more than it helps.\n\nA data race is concurrent access to one memory location with at least one write and no synchronization order. Channel sends, mutex unlock/lock, and atomic operations create useful ordering. The detector only covers executed paths:",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "reading",
        "context",
        "channel",
        "errors",
        "return",
        "value",
        "device",
        "goroutine",
        "work",
        "articles"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/en/articles/go-intensive"
      }
    },
    {
      "id": "148a92d04c723765",
      "url": "https://imrafaeldev.site/en/articles/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 4)",
      "content": "I would start looking at Go when the task is clearly CPU bound: high-volume data computation, batch image processing, heavy aggregations, compression, large buffer transforms, simulations, or any routine where the machine spends more time computing than waiting for external responses.\n\nIn that kind of scenario, goroutines with a worker pool give better control over CPU use. They also make the separation between work units, synchronization, and result collection more explicit.\n\nBut Go also charges a price. You must think about chunk granularity, memory consumption, cancellation, error handling, backpressure, worker limits, contention, and result consistency.\n\nIf the work split is naive, the CPU gain can arrive with memory blowups or needless complexity.\n\n## Counterargument: Node.js also has worker threads\n\nThere is a fair counterargument: Node.js is not limited to the Event Loop for everything. Worker threads exist precisely to run heavy work outside the main thread. There are also strategies with queues, separate processes, helper services, and native addons.\n\nSo the honest comparison is not “Node.js cannot”. It can.\n\nThe question is implementation cost, team maturity, observability, integration with the existing system, and how much effort is worth investing to keep that processing inside the Node ecosystem.\n\nOn some teams, worker threads may be enough and cheaper than introducing Go. On others, splitting CPU-bound processing into a Go service may be simpler to operate and scale.\n\nThe decision should not come from language preference. It should come from the nature of the load.\n\n## The practical rule that stuck\n\nAfter that case, the rule that stuck for me is this: if the problem is waiting on many things at once, Node.js with the Event Loop tends to orchestrate very well. If the problem is computing many things at once, I consider Go with goroutines earlier.",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "loop",
        "event",
        "problem",
        "work",
        "goroutines",
        "same",
        "concurrency"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/en/articles/goroutines-vs-event-loop"
      }
    },
    {
      "id": "151dbefa8b721552",
      "url": "https://imrafaeldev.site/es/articulos/go-intensivo",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 3)",
      "content": "Una goroutine por mensaje se vuelve costosa cuando un downstream se ralentiza. Crecen las colas y el heap, el GC trabaja más y el proceso puede caer antes de que la CPU parezca llena.\n\nerrgroup combina espera, propagación del primer error y cancelación compartida. Pon un límite para tareas independientes:\n\nfunc ProcessBatch(ctx context.Context, batch []Reading) error {\ng, ctx := errgroup.WithContext(ctx)\ng.SetLimit(16)\n\nfor _, reading := range batch {\nreading := reading\ng.Go(func() error {\nreturn processOne(ctx, reading)\n})\n}\nreturn g.Wait()\n}\nClasifica los errores antes de actuar. Una base indisponible puede cancelar un lote. Un payload inválido, duplicado o incompatible con el schema debe ir a cuarentena o DLQ, sin detener el consumer completo.\n\nUsa atomic para un contador o flag independiente, sync.Mutex para una invariante entre campos y channel para transferir trabajo. No copies un mutex después del primer uso ni mantengas un lock durante I/O remoto.\n\nBackpressure es una política de producto y operación. Si la ingesta recibe 50 mil mensajes por segundo y la persistencia completa 20 mil, guardar el resto en memoria solo desplaza el incidente. Decide si bloquear productor, rechazar con retry, pausar consumo para que el broker retenga backlog durable, descartar muestras antiguas, agregar datos o persistir en disco. Define tamaño de cola, métrica de ocupación, timeout y acción de saturación.\n\n## Pipeline IoT resistente a reentregas\n\ndispositivo -> MQTT/broker -> ingesta Go -> stream -> procesadores -> almacenamiento\n\\-> DLQ \\-> estado actual\nMQTT encaja en la conectividad de dispositivos. Un stream como Kafka encaja en retención durable, replay y particionamiento interno. gRPC es RPC interno tipado; WebSocket actualiza dashboards. Resuelven fronteras distintas.",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "return",
        "context",
        "puede",
        "orden",
        "channel",
        "concurrencia",
        "más",
        "value"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/go-intensivo"
      }
    },
    {
      "id": "15f853b2077e2ce6",
      "url": "https://imrafaeldev.site/cases/infosistemas-mensageria-es",
      "title": "Infosistemas: contrato de fallo en la mensajería RabbitMQ (Part 2)",
      "content": "El rediseño colocó estos mecanismos en los flujos críticos: publicación confirmada, cola durable con prefetch controlado, consumidor idempotente, retry con backoff y DLQ por flujo. La operación pasó a tener un camino predecible para el fallo temporal y para el fallo permanente, en lugar de depender de reprocesamiento ad hoc.\n\n## Resultado\n\nLa reducción registrada en los fallos intermitentes de los flujos críticos entre microservicios fue de aproximadamente el 98%. El número describe esos flujos tras el rediseño, no la operación entera de la empresa ni otros frentes.\n\n## Limitaciones\n\nLas métricas de otros frentes aún pendientes de método o confirmación quedan fuera. Si la volumetría o el mapa de microservicios cambia de forma que DLQ y prefetch dejen de aislar el fallo, el tuning debe revisarse con telemetría de colas y de consumidores.",
      "description": "Rediseño de la mensajería RabbitMQ en Infosistemas con colas durables, DLQ, retry, idempotencia y prefetch. El trabajo redujo en cerca del 98% los fallos intermitentes entre microservicios.",
      "keywords": [
        "para",
        "fallo",
        "prefetch",
        "flujos",
        "otros",
        "frentes",
        "críticos",
        "microservicios",
        "operación",
        "consumidores"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "infosistemas-mensageria",
        "slug": "mensajeria-rabbitmq",
        "title": "Infosistemas: contrato de fallo en la mensajería RabbitMQ",
        "description": "Rediseño de la mensajería RabbitMQ en Infosistemas con colas durables, DLQ, retry, idempotencia y prefetch. El trabajo redujo en cerca del 98% los fallos intermitentes entre microservicios.",
        "company": "Infosistemas",
        "role": "Ingeniero de Software Sénior / Arquitecto de Software",
        "period": "feb/2025 – may/2026",
        "excerpt": "Fallos intermitentes entre microservicios sin contrato para retry, DLQ o duplicidad. Más consumidores solo desplazaban la sobrecarga.",
        "proofValue": "~98%",
        "proofLabel": "reducción de fallos intermitentes en los flujos críticos",
        "featuredClaimId": "infosistemas-rabbitmq-reliability",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/infosistemas-mensageria-es.md"
      }
    },
    {
      "id": "1608d4ae3901fb2a",
      "url": "https://imrafaeldev.site/en/articles/backend-performance-close-to-data",
      "title": "Most backend performance problems start close to the data (Part 5)",
      "content": "Before moving that stage, two bills had to balance. The database should keep doing the slicing that used the indexes. The application should compose the result without fetching too many rows, creating per-item",
      "description": "Before adding machines, cache, or queues, measure the request",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "before",
        "with",
        "where",
        "start",
        "column",
        "data"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/en/articles/backend-performance-close-to-data"
      }
    },
    {
      "id": "1725994e687bde61",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 9)",
      "content": "// core/protocols/UpdateUserProtocol.ts\nexport abstract class UpdateUserProtocol {\n  abstract update(id: string, name: string): Promise<UserEntity>;\n}\n\n// core/protocols/DeleteUserProtocol.ts\nexport abstract class DeleteUserProtocol {\n  abstract delete(id: string): Promise<void>;\n}\n```\n\n## Conclusão\n\nSeparar Core, Adapters e Infra deixa a regra de negócio testável sem Nest, Prisma ou HTTP. Trocar banco ou canal de entrada vira troca de adapter e wiring de DI — não reescrita do usecase. O custo é mais arquivos e disciplina de fronteira; o ganho aparece quando o sistema precisa mudar sem arrastar o domínio junto.\n\nPublicado originalmente no [Medium](https://medium.com/@contato.dev.rafael.pereira/typescript-cleanarch-668935d677c2) (15/03/2023).",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 8,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "174851e4ad12c9ec",
      "url": "https://imrafaeldev.site/artigos/intensivao-golang-avancado",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 2)",
      "content": "**Falha ou limite que ele trata.** O modelo evita o custo de uma thread por tarefa concorrente e reduz troca de contexto do sistema operacional. O limite aparece quando se confunde concorrência com paralelismo: criar milhares de goroutines bloqueadas em I/O lento é barato, mas criar milhares de goroutines em loop apertado de CPU com GOMAXPROCS baixo apenas serializa o trabalho e aumenta pressão sobre o escalonador e o coletor de lixo.\n\n**Exemplo de aplicação.** Cenário didático: processar uma lista de itens independentes em paralelo, limitada ao número de CPUs.\n\npackage main\n\nimport (\n\"fmt\"\n\"runtime\"\n\"sync\"\n)\n\nfunc process(item int) int {\n// Simula transformação pura de CPU.\nreturn item * item\n}\n\nfunc main() {\nitems := []int{1, 2, 3, 4, 5, 6, 7, 8}\nresults := make([]int, len(items))\n\nnumWorkers := runtime.GOMAXPROCS(0)\njobs := make(chan int)\n\nvar wg sync.WaitGroup\nfor w := 0; w &#x3C; numWorkers; w++ {\nwg.Add(1)\ngo func() {\ndefer wg.Done()\nfor index := range jobs {\nresults[index] = process(items[index])\n}\n}()\n}\nfor i := range items {\njobs &#x3C;- i\n}\nclose(jobs)\nwg.Wait()\nfmt.Println(results)\n}\n**Quando escolher outra abordagem.** Mantenha o valor default de GOMAXPROCS; se considerar sobrescrevê-lo, meça antes e depois em benchmarks controlados. Para tarefas puramente sequenciais, dependentes entre si ou com overhead de coordenação maior que o ganho, o laço simples sem goroutines é mais legível e mais rápido. Para paralelismo de dados em lote com cancelamento e limite de erro, prefira errgroup ou um pool com semáforo em vez de disparar goroutines sem controle.\n\n## 2. Ownership e cancelamento com context",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "goroutines",
        "sync",
        "func",
        "contexto",
        "quando",
        "done",
        "mutex",
        "limite",
        "context"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-golang-avancado"
      }
    },
    {
      "id": "17a0bdf393fa32c4",
      "url": "https://imrafaeldev.site/en/articles/derusting-logic-container-with-most-water",
      "title": "Derusting logic #02: Container With Most Water (Part 3)",
      "content": "The advancing detail matters. In the version I had written, the pointers only advanced when the current area was not larger than maxArea. If a new maximum area was found, the same combination would be computed again, never leaving the loop. The fix was to separate the two decisions: record the area, then move the shorter-height pointer.\n\n## The result of the attempts\n\nThe LeetCode history looked like this:\n\n- JavaScript: accepted, 3 ms and 63.6 MB.\n\n- JavaScript: wrong answer.\n\n- JavaScript: wrong answer.\n\n- Go: accepted, 0 ms and 9.6 MB.\n\n- TypeScript: accepted, 3 ms and 63.9 MB.\n\n- Go: wrong answer.\n\nThere were three wrong-answer attempts before reaching the accepted solutions in JavaScript, Go, and TypeScript. More than counting submissions, I wanted to look at the error, understand the hypothesis that failed, and try again without outsourcing all the reasoning.\n\n## Complexity\n\nThe algorithm walks the array once. On each iteration, one of the pointers advances, so time complexity is O(n) and space complexity is O(1).\n\nThe first attempt also used two pointers but did extra work comparing future possibilities. The second solution uses a property of the problem to safely discard part of the combinations.\n\nThat was the exercise this time: not mistaking a choice that looks good now for a decision the problem actually lets you justify.\n\n---\n\n*Derusting logic series #02 — Container With Most Water. Problem at [leetcode.com/problems/container-with-most-water](https://leetcode.com/problems/container-with-most-water/).*\n\nGeometria: min da altura × largura\nNas posições 1 e 8, a menor altura é 7 e a largura é 7. O recipiente produz área 49.\n\nDois ponteiros: mova a menor linha\nA largura sempre diminui. Só mover a menor altura pode encontrar um limite maior; mover a maior mantém o gargalo e pode ser descartado.",
      "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
      "keywords": [
        "heights",
        "area",
        "leftindex",
        "rigthindex",
        "that",
        "height",
        "container",
        "with",
        "maxarea",
        "problem"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/en/articles/derusting-logic-container-with-most-water"
      }
    },
    {
      "id": "17dd176e5d447562",
      "url": "https://imrafaeldev.site/es",
      "title": "Rafael Pereira, ingeniero de software sénior (Part 4)",
      "content": "El camino elegido, con la alternativa descartada a la vista.\n\n04\n\n### Evidencia\n\nQué se midió, en qué escenario, con qué autoría.\n\n05\n\n### Límite\n\nLa condición que justificaría revisar la decisión.\n\nContacto\n\n## ¿Tienes un sistema que dejó de ser simple?\n\nEscríbeme directo por @imrafaeldev, sin formulario — para conversación profesional, empieza en LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/) [YouTube](https://www.youtube.com/@imrafaeldev) [GitHub](https://github.com/imrafaeldev) [LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir la página de contacto](/es/contacto/)",
      "description": "Portafolio institucional y hub editorial de Rafael Pereira. Trabajo en los puntos en los que los sistemas simples dejan de ser simples.",
      "keywords": [
        "casos",
        "proyectos",
        "abrir",
        "case",
        "qué",
        "caso",
        "fallos",
        "decisión",
        "contacto",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/es"
      }
    },
    {
      "id": "189a5ebbaa9122e3",
      "url": "https://imrafaeldev.site/es/articulos/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 3)",
      "content": "// core/features/CreateUser.ts\nexport abstract class CreateUser {\nabstract execute(name: string): Promise&#x3C;UserEntity>;\n}\n// core/usecases/CreateUserUsecase.ts\nexport class CreateUserUsecase implements CreateUser {\nconstructor(\nprivate readonly createUserProtocol: CreateUserProtocol,\nprivate readonly getByNameProtocol: GetUserByNameProtocol,\n) {}\n\nasync execute(name: string): Promise&#x3C;UserEntity> {\nconst existsName = await this.getByNameProtocol.getByName(name);\n\nif (existsName) {\nthrow new UserAlreadyExistsException(\n`the name ${name} already exists`,\n);\n}\n\nreturn this.createUserProtocol.register(name);\n}\n}\nEl usecase define *qué* (validar nombre, registrar). No define *cómo* buscar o persistir. La regla queda independiente de lib, framework y base.\n\nCuidado: un usecase que solo delega al protocol sin validar puede estar empujando regla de negocio hacia el adapter. En CreateUserUsecase, la verificación de nombre duplicado es obligación del Core.\n\n### Exceptions\n\nUserAlreadyExistsException pertenece al Core: el flujo inválido de la regla también es regla. Cada fallo mapeado a una excepción conocida ayuda al mantenimiento. Base con code (después se vuelve status HTTP en el borde):\n\n// core/exceptions/IBaseException.ts\nexport abstract class IBaseException extends Error {\ncode: number;\n\nconstructor(message: string) {\nsuper(message);\n}\n}\n// core/exceptions/UserAlreadyExistsException.ts\nexport class UserAlreadyExistsException extends IBaseException {\nconstructor(message?: string) {\nsuper(message ?? \"User already exists\");\nthis.code = 400;\n}\n}\nEl usecase **lanza** excepciones; **no** las trata. Mapear tipo desconocido → tipo conocido queda en adapter o infra.\n\n### Protocols\n\nCreateUserProtocol y GetUserByNameProtocol son contratos de acceso a dispositivo externo. El protocol existe para informar o disparar acción externa — **no** para procesar regla de negocio. Preferencia: un método público por protocol.",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "regla",
        "export",
        "architecture",
        "para",
        "class"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/typescript-clean-architecture"
      }
    },
    {
      "id": "18b8bd7265f4bf7c",
      "url": "https://imrafaeldev.site/es/proyectos",
      "title": "Proyectos (Part 2)",
      "content": "## [Post Engine](/es/proyectos/post-engine/)\n\nLa entrevista adaptativa extrae evidencias; el gateway híbrido (LLM + heurística) bloquea vivencia inventada. Solo el contenido confirmado sigue a borrador y exportación en Markdown o SlideMark.\n\n[Abrir el proyecto](/es/proyectos/post-engine/)\n\nAutoria antes da geração\nEntrevista extrai evidência; briefing e storyboard preparam o material; o gateway veta fabricado. Só o confirmado segue para rascunho e export.\n\n## Otros trabajos, otros contextos\n\nProyectos visuales para explorar manualmente.\n\n-\n[Gran Goiás Sitio institucional de Gran Goiás, marmolería con ejecución en piedra para obras de escala. Visitar sitio](https://gran-goias.vercel.app/)\n\n-\n[Gabriel | Nutrición Deportiva Sitio institucional de Gabriel Pereira para nutrición deportiva, con contenido real y estr](https://gabriel-pereira-nutri.vercel.app/)",
      "description": "Artefactos con problema, restricciones y estado actual del repositorio.",
      "keywords": [
        "proyectos",
        "abrir",
        "proyecto",
        "sqlite",
        "local",
        "diffvision",
        "para",
        "estado",
        "repositorio",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/es/proyectos"
      }
    },
    {
      "id": "19ad67a50fedf920",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-container-with-most-water-pt-br",
      "title": "Desenferrujando a lógica #02: Container With Most Water (Part 3)",
      "content": "Foram três tentativas com resposta incorreta antes de chegar às soluções aceitas em JavaScript, Go e TypeScript. Mais do que contar submissões, eu queria olhar para o erro, entender a hipótese que falhou e tentar de novo sem terceirizar todo o raciocínio.\n\n## Complexidade\n\nO algoritmo percorre o array uma vez. Em cada iteração, um dos ponteiros avança, então a complexidade de tempo é `O(n)` e a complexidade de espaço é `O(1)`.\n\nA primeira tentativa também usava dois ponteiros, mas fazia trabalho extra para comparar possibilidades futuras. A segunda solução usa uma propriedade do problema para descartar com segurança parte das combinações.\n\nEsse foi o exercício desta vez: não confundir uma escolha que parece boa agora com uma decisão que o problema realmente permite justificar.\n\n---\n\n*Série Desenferrujando a lógica #02 — Container With Most Water. Problema em [leetcode.com/problems/container-with-most-water](https://leetcode.com/problems/container-with-most-water/).*",
      "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
      "keywords": [
        "área",
        "heights",
        "leftindex",
        "rigthindex",
        "altura",
        "ponteiros",
        "maxarea",
        "para",
        "dois",
        "javascript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "desenferrujando-logica-container-with-most-water",
        "title": "Desenferrujando a lógica #02: Container With Most Water",
        "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
        "excerpt": "Eu tentei escolher o próximo passo olhando apenas para os vizinhos. Funcionou em alguns casos, mas o problema pedia uma visão mais ampla.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-container-with-most-water-pt-br.md"
      }
    },
    {
      "id": "1a2ac6823f3dda20",
      "url": "https://imrafaeldev.site/artigos/backend-performance-perto-dos-dados",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 5)",
      "content": "O banco passou a reduzir o conjunto cedo. A aplicação recebia os registros selecionados e compunha os indicadores. Depois da mudança, o dashboard deixou de depender daquela consulta pesada e ficou mais previsível, porque a divisão acompanhava o ponto em que o custo crescia no plano.\n\nEssa fronteira não veio de uma preferência por query única ou por mais lógica na aplicação.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "antes",
        "execução",
        "dados",
        "não",
        "trabalho"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/artigos/backend-performance-perto-dos-dados"
      }
    },
    {
      "id": "1aecd9f411d5c730",
      "url": "https://imrafaeldev.site/es/articulos/backend-performance-cerca-de-los-datos",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 5)",
      "content": "La composición final de los indicadores fue a la aplicación. Esa etapa venía después de las relaciones y agregaciones que elevaban el volumen intermedio y mantenían alta la CPU de la base. Con los datos ya recortados, el servidor podía montar los valores de la pantalla sin concentrar toda la ejecución en una única query.\n\nLa base pasó a reducir el conjunto",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "base",
        "consulta",
        "para",
        "plan",
        "puede",
        "antes",
        "ejecución",
        "datos",
        "trabajo",
        "cuando"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/backend-performance-cerca-de-los-datos"
      }
    },
    {
      "id": "1b5658ea78df1828",
      "url": "https://imrafaeldev.site/artigos/design-patterns-adapter",
      "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter\n\n## Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter\n\nMigrar de MySQL para PostgreSQL não precisa reescrever o serviço. O Adapter isola o plugin atrás de um contrato que a regra de negócio entende.\n\n26 de abril de 2022\n\n- [Arquitetura](/artigos/?topic=arquitetura)\n\nCom o padrão Adapter, isolamos a regra de negócio da dependência concreta.\n\n## Cenário inicial\n\nTemos um backend com CRUD simples de usuário: criar, editar, recuperar e deletar por endpoints na API. Os dados vão para um banco qualquer — digamos MySQL — e a estrutura nasce como **Controller → Service → Database**. Até aqui, o time está confortável.\n\nAté alguém decidir: na semana que vem migramos de MySQL para PostgreSQL. A partir daí a casa cai para tecnologia. Além de reestruturar o banco, o time precisa caçar referências ao MySQL: inserções, conexão, queries espalhadas.\n\nNa maioria das vezes isso atrasa entrega, reduz qualidade, pula testes e introduz bugs. Seria melhor diminuir a dependência entre a regra de negócio e quem executa a operação específica — no caso, o banco. O Adapter ajuda a construir o sistema assim.\n\n## Padrão Adapter\n\nTemos um plugin, library, module ou serviço de terceiros que faz algo que queremos na regra de negócio — aqui, persistir dados. O caminho:\n\n- Definir uma interface com o contrato do que precisamos.\n\n- Expor só métodos que fazem sentido no contexto (SOLID).\n\n- Implementar a interface em classes que adaptam o código de terceiros.\n\nCriamos CreateDatabaseCustomerProtocol com um método create que recebe CustomerInputEntity e retorna SuccessfulEntityCreation:\n\ninterface CustomerInputEntity {\nname: string;\nemail: string;\nbirthDate: Date;\n}\n\ninterface SuccessfulEntityCreation {\nreadonly id: number;\nreadonly name: string;\nreadonly email: string;\nreadonly birthDate: Date;\n}",
      "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
      "keywords": [
        "adapter",
        "para",
        "serviço",
        "regra",
        "negócio",
        "contrato",
        "banco",
        "interface",
        "mysql",
        "plugin"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/artigos/design-patterns-adapter"
      }
    },
    {
      "id": "1c354b64a5498521",
      "url": "https://imrafaeldev.site/en/articles/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 5)",
      "content": "export const mockDbConnector: DbConnector = {\nusers: {\ngetById: async (id: string) =>\nPromise.resolve(new UserEntity({ id, name: \"Test\" })),\ngetByName: async (name: string) =>\nPromise.resolve(new UserEntity({ id: \"1\", name })),\nregister: async (name: string) =>\nPromise.resolve(new UserEntity({ id: \"2\", name })),\nupdate: async (id: string, name: string) =>\nPromise.resolve(new UserEntity({ id, name })),\ndelete: async (_id: string) => Promise.resolve(),\n},\nprofiles: {\ngetById: async (_id: string) => Promise.resolve(null),\ngetByName: async (_name: string) => Promise.resolve(null),\nregister: async (_name:",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "architecture",
        "return",
        "export",
        "class",
        "adapters"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/en/articles/typescript-clean-architecture"
      }
    },
    {
      "id": "1cb6f71b6e1ea50a",
      "url": "https://imrafaeldev.site/artigos",
      "title": "Artigos (Part 1)",
      "content": "- [Início](/)\n- Artigos\n\n## Artigos\n\nPadrões e decisões de engenharia em prosa técnica, com código.\n\nTodosPerformanceArquiteturaTrade-offsMensageria\n\n-\n\n## [Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos](/artigos/intensivao-go/)\n\n22 de setembro de 2026\n\n[Arquitetura](/artigos/?topic=arquitetura)\n- [Performance](/artigos/?topic=performance)\n- [Mensageria](/artigos/?topic=mensageria)\nUm roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.\n\n-\n\n## [Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs](/artigos/intensivao-golang-avancado/)\n\n22 de setembro de 2026\n\n[Arquitetura](/artigos/?topic=arquitetura)\n- [Performance](/artigos/?topic=performance)\n- [Trade-offs](/artigos/?topic=trade-offs)\nO passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.\n\n-\n\n## [Desenferrujando a lógica #02: Container With Most Water](/artigos/desenferrujando-logica-container-with-most-water/)\n\n15 de setembro de 2026\n\n[Performance](/artigos/?topic=performance)\n- [Trade-offs](/artigos/?topic=trade-offs)\nEu tentei escolher o próximo passo olhando apenas para os vizinhos. Funcionou em alguns casos, mas o problema pedia uma visão mais ampla.\n\n-\n\n## [Desenferrujando a lógica #01: Group Anagrams](/artigos/desenferrujando-logica-group-anagrams/)\n\n17 de agosto de 2026\n\n[Trade-offs](/artigos/?topic=trade-offs)\n- [Performance](/artigos/?topic=performance)\nEu não tinha parado de escrever código. O que mudou foi terceirizar partes do raciocínio. Group Anagrams foi o exercício para recuperar o hábito.\n\n-\n\n## [A maioria dos problemas de performance de backend começa perto dos dados](/artigos/backend-performance-perto-dos-dados/)\n\n16 de julho de 2026",
      "description": "Padrões e decisões de engenharia em prosa técnica, com código.",
      "keywords": [
        "artigos",
        "topic",
        "arquitetura",
        "performance",
        "trade-offs",
        "2026",
        "para",
        "concorrência",
        "não",
        "setembro"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/artigos"
      }
    },
    {
      "id": "1cd4318cc0f681ab",
      "url": "https://imrafaeldev.site/artigos/desenferrujando-logica-group-anagrams",
      "title": "Desenferrujando a lógica #01: Group Anagrams (Part 2)",
      "content": "result := make([][]string, 0, len(mapping))\n\nfor _, group := range mapping {\nresult = append(result, group)\n}\n\nreturn result\n}\nO que acontece nesse código:\n\n- sortString(str) transforma a string em bytes, ordena os caracteres e devolve uma nova string.\n\n- sortedStr := sortString(str) produz a chave daquele termo.\n\n- mapping[sortedStr] = append(...) usa essa chave para acumular os anagramas no mesmo grupo.\n\n- Para eat, tea e ate, sortedStr será sempre aet.\n\nO mapa começa a ficar assim:\n\n\"aet\" -> [\"eat\", \"tea\", \"ate\"]\n\"ant\" -> [\"tan\", \"nat\"]\n\"abt\" -> [\"bat\"]\nCalculo uma chave e adiciono a palavra diretamente ao grupo correspondente. O map evita comparar cada string com todas as outras.\n\n## Onde está o custo dessa abordagem?\n\nPara descobrir a chave, preciso ordenar cada string. Se uma string tem k caracteres, essa ordenação custa aproximadamente O(k log k). Repetindo para n strings, a parte dominante fica em O(n × k log k).\n\n### Por que retirar o sort?\n\nPara eat, eu ordenava os caracteres para chegar em aet. Para tea, fazia outra ordenação para chegar no mesmo aet. O sort funciona porque cria uma representação comum. Só que o problema não exige ordenar nada.\n\nPara saber se duas strings são anagramas, basta verificar se possuem a mesma quantidade de cada letra.\n\n## Essa observação muda a solução\n\nEm vez de colocar as letras na mesma ordem, posso contar quantas vezes cada uma aparece. Nesse exercício, as entradas usam letras minúsculas de a até z. Cada string pode ser representada por 26 contadores.\n\ntea e ate produzem a mesma contagem. Não preciso reorganizar nenhum caractere. Só percorro a string e conto as ocorrências. Para eat, a parte relevante fica:\n\na = 1\ne = 1\nt = 1\n\n### Solução usando contagem\n\nvar key [26]uint8 cria 26 posições, uma para cada letra de a até z. key[str[i]-'a']++ encontra a posição de cada caractere e incrementa o contador. Depois, groups[key] = append(groups[key], str) usa o próprio vetor de frequências como chave do grupo.",
      "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
      "keywords": [
        "string",
        "para",
        "chave",
        "cada",
        "group",
        "não",
        "solução",
        "result",
        "problema",
        "sort"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/artigos/desenferrujando-logica-group-anagrams"
      }
    },
    {
      "id": "1cd93a16c20d5239",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 1)",
      "content": "Software development changes all the time. Weak architecture becomes expensive maintenance, slow features, hard tests, and bugs that are hard to isolate. It is worth investing in a structure that supports evolution without rewriting the system at every business pressure.\n\n## A bit of history\n\nClean Architecture is the name Robert C. Martin (Uncle Bob) gave, in 2012, in the book *Clean Architecture: A Craftsman's Guide to Software Structure and Design*. The proposal avoids the rigidity of architectures coupled to framework and database: the core stays stable; external details change. The idea draws from DDD, SOLID, Onion Architecture, and Hexagonal Architecture.\n\n## General proposal\n\nThis article describes Clean Architecture and a practical derivation for TypeScript backends: three layers — **Core**, **Adapters**, and **Infra**.\n\n- **Core** — business rules and domain entities. Innermost layer.\n- **Infra** — external connections: concrete repositories, REST controllers, DI modules, framework boilerplate.\n- **Adapters** — mediation in both directions. A controller does not call a \"raw\" usecase: it goes through a service. A usecase does not talk to the database: it talks to a protocol that an adapter (repository, connector, handler) implements.\n\nEach layer has different capabilities and constraints; SOLID weighs more in Core. It works for HTTP CRUD and for systems with several frameworks and channels.\n\nConcrete benefits: clear responsibilities (reading and maintenance), flexibility to swap plugins without rewriting rules, and isolated tests per layer.\n\n## Layer guide\n\nExample: user CRUD over REST with NestJS. Installation details are out of scope. **Core-to-infra** writing (inside out).\n\n## Core\n\nIn the classic design, *domain* and *entities* sit very close. Here they form the **Core**: everything the business rule *is* — features and domain representations.\n\nIn the example, the main entity is User (`id`, `name`), in `core/entities`.\n\n### Entities",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "1d400d21592daf96",
      "url": "https://imrafaeldev.site/es/articulos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 5)",
      "content": "En algunos equipos, usar worker threads puede bastar y ser más barato que introducir Go. En otros, separar el procesamiento CPU bound en un servicio Go puede ser más simple de operar y escalar.\n\nLa decisión no debería nacer de preferencia por",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "loop",
        "mismo",
        "trabajo",
        "más",
        "goroutines",
        "event",
        "concurrencia",
        "problema"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "1dfd586d59d67e4d",
      "url": "https://imrafaeldev.site/projects/sms-manager-es",
      "title": "SMS Manager",
      "content": "## Problema\n\nLas campañas de SMS parten de archivos CSV, usuarios, empresas y autenticación entre servicios. Validar y persistir en el mismo proceso que dispara miles de mensajes acopla la API al ritmo de la operadora y de la cola.\n\n## Restricciones\n\nEl entorno debe ser reproducible. La autenticación entre servicios no puede depender de JWT opaco sin revocación. Los consumidores en más de un lenguaje existen para comparar el mismo contrato de mensajería.\n\n## Decisión\n\nSiete aplicaciones: APIs NestJS de usuarios/autenticación y de empresas/campañas; consumidores equivalentes en Node.js/TypeScript, Go y Rust; aprovisionador declarativo de exchanges, colas y bindings; generador de masa CSV. PostgreSQL/TypeORM para datos relacionales en la API; MongoDB para el resultado del consumo; Redis para caché de token opaco (revocable); gRPC para autenticación entre servicios; RabbitMQ con exchanges topic para los lotes. La API publica y sigue sin bloquearse al ritmo de la operadora.\n\n## Estado actual\n\nRepositorio público con Docker Compose para PostgreSQL, MongoDB, Redis y RabbitMQ. La arquitectura separa dominio, aplicación e infraestructura en los contextos de usuarios, autenticación y empresas; los tres consumidores implementan el mismo contrato de mensaje.\n\n## Limitaciones\n\nEs un artefacto de estudio y operación local de mensajería, no un producto comercial con SLA de operadora. Los tres consumidores demuestran el contrato; no afirman que los tres lenguajes corran juntos en producción del autor.",
      "description": "Campañas de SMS desacopladas: la API Nest persiste y publica, la cola entrega y consumidores en TypeScript, Go o Rust graban el resultado. Token opaco, Redis y gRPC entre servicios.",
      "keywords": [
        "para",
        "autenticación",
        "consumidores",
        "usuarios",
        "empresas",
        "entre",
        "servicios",
        "mismo",
        "operadora",
        "contrato"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "projeto-sms-manager",
        "slug": "sms-manager",
        "title": "SMS Manager",
        "description": "Campañas de SMS desacopladas: la API Nest persiste y publica, la cola entrega y consumidores en TypeScript, Go o Rust graban el resultado. Token opaco, Redis y gRPC entre servicios.",
        "excerpt": "Importar CSV no puede bloquear la API Nest. La campaña persiste y publica en la cola RabbitMQ; consumidores en TypeScript, Go o Rust graban el resultado en Mongo. La API no espera a la operadora.",
        "repoUrl": "https://github.com/imrafaeldev/sms-manager",
        "status": "Repositorio público",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/sms-manager-es.md"
      }
    },
    {
      "id": "1e5c3ab613d1aa92",
      "url": "https://imrafaeldev.site/en/projects/sms-manager",
      "title": "SMS Manager (Part 2)",
      "content": "It is a study artifact and local messaging operation, not a commercial product with a carrier SLA. The three consumers demonstrate the contract; they do not claim the three languages run together in the author’s production.",
      "description": "Decoupled SMS campaigns: the Nest API persists and publishes, the queue delivers, and consumers in TypeScript, Go, or Rust record the result. Opaque token, Redis, and gRPC between services.",
      "keywords": [
        "consumers",
        "carrier",
        "authentication",
        "repository",
        "rabbitmq",
        "between",
        "services",
        "same",
        "contract",
        "with"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/projects/sms-manager"
      }
    },
    {
      "id": "1e9743752c011a6e",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-es",
      "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter (Part 1)",
      "content": "Con el patrón Adapter, aislamos la regla de negocio de la dependencia concreta.\n\n## Escenario inicial\n\nTenemos un backend con CRUD simple de usuario: crear, editar, recuperar y eliminar por endpoints en la API. Los datos van a una base cualquiera — digamos MySQL — y la estructura nace como **Controller → Service → Database**. Hasta aquí, el equipo está cómodo.\n\nHasta que alguien decide: la semana que viene migramos de MySQL a PostgreSQL. A partir de ahí la casa se cae para tecnología. Además de reestructurar la base, el equipo necesita cazar referencias a MySQL: inserciones, conexión, queries esparcidas.\n\nLa mayoría de las veces esto retrasa la entrega, reduce la calidad, salta pruebas e introduce bugs. Sería mejor disminuir la dependencia entre la regla de negocio y quien ejecuta la operación específica — en este caso, la base. El Adapter ayuda a construir el sistema así.\n\n## Patrón Adapter\n\nTenemos un plugin, library, module o servicio de terceros que hace algo que queremos en la regla de negocio — aquí, persistir datos. El camino:\n\n1. Definir una interfaz con el contrato de lo que necesitamos.\n2. Exponer solo métodos que tengan sentido en el contexto (SOLID).\n3. Implementar la interfaz en clases que adaptan el código de terceros.\n\nCreamos `CreateDatabaseCustomerProtocol` con un método `create` que recibe `CustomerInputEntity` y retorna `SuccessfulEntityCreation`:\n\n```typescript\ninterface CustomerInputEntity {\n  name: string;\n  email: string;\n  birthDate: Date;\n}\n\ninterface SuccessfulEntityCreation {\n  readonly id: number;\n  readonly name: string;\n  readonly email: string;\n  readonly birthDate: Date;\n}\n\ninterface CreateDatabaseCustomerProtocol {\n  createCustomerOnDatabase(\n    customer: CustomerInputEntity,\n  ): SuccessfulEntityCreation;\n}\n```\n\nEn lugar de **Controller → Service → Database**, pasamos a **Controller → Service → Protocols → Plugin**.",
      "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
      "keywords": [
        "adapter",
        "regla",
        "negocio",
        "base",
        "servicio",
        "readonly",
        "contrato",
        "createdatabasecustomerprotocol",
        "customerinputentity",
        "successfulentitycreation"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter",
        "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
        "excerpt": "Migrar de MySQL a PostgreSQL no exige reescribir el servicio. El Adapter aísla el plugin tras un contrato que la regla de negocio entiende.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-es.md"
      }
    },
    {
      "id": "1ef4e7cf80f1a003",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 1)",
      "content": "Este roteiro serve para quem já trabalha com backend e quer reativar Go para uma conversa técnica ou um serviço de produção. O foco está nas decisões que mantêm um sistema previsível sob carga: concorrência limitada, cancelamento, filas finitas, idempotência e observabilidade.\n\nO recorte usa Go 1.26. Não é uma introdução à linguagem. Passe rápido pelos fundamentos e retenha os pontos que mudam o desenho de um consumer, uma API ou um pipeline de telemetria.\n\n## Roteiro de 30 minutos\n\n| Tempo | Bloco | Prioridade |\n| --- | --- | --- |\n| 0-4 min | Tipos, structs, interfaces e erros | Revisão rápida |\n| 4-10 min | Goroutines, channels, `select` e contexto | Alta |\n| 10-17 min | Limites de concorrência e backpressure | Máxima |\n| 17-23 min | Pipeline IoT resiliente | Máxima |\n| 23-26 min | Runtime, memória e profiling | Alta |\n| 26-30 min | Arquitetura e perguntas de entrevista | Máxima |\n\n## Fundamentos que aparecem em produção\n\nGo favorece composição, contratos pequenos e fluxo explícito. Não há herança de classes nem exceções como mecanismo normal de controle. Um tipo simples pode carregar sua própria validação:\n\n```go\npackage telemetry\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\nvar ErrOutOfRange = errors.New(\"reading out of range\")\n\ntype Reading struct {\n\tDeviceID   string    `json:\"device_id\"`\n\tSequence   uint64    `json:\"sequence\"`\n\tObservedAt time.Time `json:\"observed_at\"`\n\tValue      float64   `json:\"value\"`\n}\n\nfunc (r Reading) Validate() error {\n\tif r.DeviceID == \"\" {\n\t\treturn errors.New(\"device_id is required\")\n\t}\n\tif r.Value < -100 || r.Value > 250 {\n\t\treturn fmt.Errorf(\"%w: %.2f\", ErrOutOfRange, r.Value)\n\t}\n\treturn nil\n}\n```\n\nAlguns detalhes evitam erros silenciosos:",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "1f29eb1bd75b0ed2",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-es",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 2)",
      "content": "1. `sortString(str)` transforma el string en bytes, ordena los caracteres y devuelve un nuevo string.\n2. `sortedStr := sortString(str)` produce la clave de aquel término.\n3. `mapping[sortedStr] = append(...)` usa esa clave para acumular los anagramas en el mismo grupo.\n4. Para `eat`, `tea` y `ate`, `sortedStr` será siempre `aet`.\n\nEl mapa empieza a quedar así:\n\n```text\n\"aet\" -> [\"eat\", \"tea\", \"ate\"]\n\"ant\" -> [\"tan\", \"nat\"]\n\"abt\" -> [\"bat\"]\n```\n\nCalculo una clave y añado la palabra directamente al grupo correspondiente. El `map` evita comparar cada string con todas las demás.\n\n## ¿Dónde está el costo de ese enfoque?\n\nPara descubrir la clave, necesito ordenar cada string. Si un string tiene `k` caracteres, esa ordenación cuesta aproximadamente `O(k log k)`. Repitiendo para `n` strings, la parte dominante queda en `O(n × k log k)`.\n\n### ¿Por qué quitar el sort?\n\nPara `eat`, ordenaba los caracteres para llegar a `aet`. Para `tea`, hacía otra ordenación para llegar al mismo `aet`. El `sort` funciona porque crea una representación común. Solo que el problema no exige ordenar nada.\n\nPara saber si dos strings son anagramas, basta verificar si poseen la misma cantidad de cada letra.\n\n## Esa observación cambia la solución\n\nEn lugar de colocar las letras en el mismo orden, puedo contar cuántas veces aparece cada una. En este ejercicio, las entradas usan letras minúsculas de `a` a `z`. Cada string puede representarse por 26 contadores.\n\n`tea` y `ate` producen el mismo conteo. No necesito reorganizar ningún carácter. Solo recorro el string y cuento las ocurrencias. Para `eat`, la parte relevante queda:\n\n```text\na = 1\ne = 1\nt = 1\n```\n\n### Solución usando conteo\n\n`var key [26]uint8` crea 26 posiciones, una para cada letra de `a` a `z`. `key[str[i]-'a']++` encuentra la posición de cada carácter e incrementa el contador. Después, `groups[key] = append(groups[key], str)` usa el propio vector de frecuencias como clave del grupo.",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "clave",
        "solución",
        "result",
        "problema",
        "groups",
        "group",
        "mismo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "desenoxidando-logica-group-anagrams",
        "title": "Desenoxidando la lógica #01: Group Anagrams",
        "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
        "excerpt": "Yo no había dejado de escribir código. Lo que cambió fue tercerizar partes del razonamiento. Group Anagrams fue el ejercicio para recuperar el hábito.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-es.md"
      }
    },
    {
      "id": "1f2c6c45656392e5",
      "url": "https://imrafaeldev.site/en/projects",
      "title": "Projects (Part 1)",
      "content": "- [Home](/en/)\n- Projects\n\n## Projects\n\nArtifacts with problem, constraints, and current repository state.\n\nPublic repository\n\n## [SMS Manager](/en/projects/sms-manager/)\n\nImporting CSV must not block the Nest API. The campaign persists and publishes to RabbitMQ; consumers in TypeScript, Go, or Rust record the result in Mongo. The API does not wait for the carrier.\n\n[Open the project](/en/projects/sms-manager/)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\nDocumented experiment\n\n## [goc_mcp](/en/projects/goc-mcp/)\n\nMaestro (Codex/Cursor) delegates over MCP; FIFO daemon and OpenCode workers execute with state in SQLite. Orchestration worked, but measurement did not confirm cost or time reduction.\n\n[Open the project](/en/projects/goc-mcp/)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\nOwn product, desktop\n\n## [md2cv](/en/projects/md2cv/)\n\nProfile and immutable versions stay in the machine's SQLite. The supervised agent only proposes changes under schema; ATS and PDF/DOCX export reuse the same graph, with no SaaS backend owning the data.\n\n[Open the project](/en/projects/md2cv/)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\nPublic CLI; AI review still mock\n\n## [DiffVision](/en/projects/diffvision/)\n\nThe Git diff opens in the local UI; comments and Markdown export stay in the repository. Visual AI review remains a mock.\n\n[Open the project](/en/projects/diffvision/)\n\nRevisão local-first\nDiff no disco, UI local, comentários e export em `.diffvision/`. A plataforma remota fica de fora; a revisão por IA visual ainda é mock.\n\nEditorial workstation\n\n## [Post Engine](/en/projects/post-engine/)",
      "description": "Artifacts with problem, constraints, and current repository state.",
      "keywords": [
        "projects",
        "export",
        "with",
        "open",
        "project",
        "sqlite",
        "local",
        "diffvision",
        "repository",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/projects"
      }
    },
    {
      "id": "2038ee3a53dd07f4",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 3)",
      "content": "O produtor fecha o channel quando não haverá novo envio. Enviar em channel fechado ou fechá-lo duas vezes causa `panic`. Receber de um channel fechado retorna o zero value e `ok == false`. Um channel `nil` bloqueia para sempre; dentro de `select`, ele desabilita o caso.\n\n`select` combina envio, cancelamento e política de saturação. Sem `default`, a operação espera espaço ou cancelamento. Com `default`, ela rejeita imediatamente quando a fila está cheia:\n\n```go\nfunc enqueue(ctx context.Context, jobs chan<- Reading, reading Reading) error {\n\tselect {\n\tcase jobs <- reading:\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\treturn context.Cause(ctx)\n\t}\n}\n```\n\n`context.Context` carrega cancelamento, deadline e metadados estritamente ligados à requisição. Receba-o como primeiro argumento, propague-o, chame todo `cancel` retornado e não guarde contexto em struct. Cancelar não encerra uma goroutine à força: os loops e operações bloqueantes precisam observar `ctx.Done()`.\n\n## Concorrência limitada antes da pressão de memória\n\nUma goroutine por mensagem parece barata até um downstream ficar lento. A fila cresce, o heap cresce, o GC trabalha mais e o processo pode cair antes de a CPU parecer saturada.\n\nPara tarefas independentes que falham juntas, `errgroup` oferece espera, propagação do primeiro erro e cancelamento compartilhado. `SetLimit` estabelece o teto de concorrência.\n\n```go\nfunc ProcessBatch(ctx context.Context, batch []Reading) error {\n\tg, ctx := errgroup.WithContext(ctx)\n\tg.SetLimit(16)\n\n\tfor _, reading := range batch {\n\t\treading := reading\n\t\tg.Go(func() error {\n\t\t\tif err := processOne(ctx, reading); err != nil {\n\t\t\t\treturn fmt.Errorf(\"device %s: %w\", reading.DeviceID, err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn g.Wait()\n}\n```\n\nClassifique o erro antes de decidir o que fazer. Um banco indisponível pode justificar cancelar o lote. Um payload inválido, duplicado ou fora do schema deve ir para quarentena ou DLQ, sem derrubar o consumer inteiro.",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "2054ca57e3899543",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 6)",
      "content": "```typescript\nexport class UserService {\n  constructor(\n    private createUserUsecase: CreateUser,\n    private updateUserUsecase: UpdateUser,\n    private deleteUserUsecase: DeleteUser,\n    private getUserUsecase: GetUser,\n  ) {}\n\n  async getUser(id: string): Promise<UserEntity> {\n    try {\n      return await this.getUserUsecase.execute(id);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async createUser(name: string): Promise<UserEntity> {\n    try {\n      return await this.createUserUsecase.execute(name);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async updateUser(id: string, name: string): Promise<UserEntity> {\n    try {\n      return await this.updateUserUsecase.execute(id, name);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async deleteUser(id: string): Promise<void> {\n    try {\n      return await this.deleteUserUsecase.execute(id);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n}\n```\n\nEl service depende de las **features** (contratos), no de las clases concretas de usecase. Mapea excepción de Core a `RestError` que la Infra traduce en respuesta HTTP.\n\nBuena práctica: services solo para Infra; un service por \"instrumento\" de entrada (controller HTTP ≠ handler de cola), salvo middleware del mismo stack que reutiliza el mismo service.\n\n## Infra\n\nFramework, DI, controllers, DTOs y boilerplate que no es negocio ni adapter.\n\nController NestJS:\n\n```typescript\n// infra/controllers/UserController.ts\n@Controller(\"users\")\nexport class UserController {\n  constructor(private service: UserService) {}\n\n  @Get(\":id\")\n  async getUser(@Param(\"id\") id: string): Promise<UserEntity> {\n    return this.service.getUser(id);\n  }",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "207f32c363778304",
      "url": "https://imrafaeldev.site/es/curriculum",
      "title": "Currículum (Part 1)",
      "content": "- [Inicio](/es/)\n- Currículum\n\n## Ingeniería para sistemas que dejaron de ser simples\n\nIngeniero Backend Senior\n\nIngeniero Backend Senior con más de siete años de experiencia en sistemas distribuidos, plataformas transaccionales y modernización de legados.\n\nCombino ejecución práctica con arquitectura, liderazgo técnico, mentoría y colaboración con producto y stakeholders. Mi eje principal es Node.js, Go y Java; React y Angular aparecen como trabajo complementario en productos que necesitan continuidad entre backend y frontend.\n\n## Experiencia\n\n- feb/2025 a may/2026 Remoto Infosistemas Ingeniero de Software Senior / Arquitecto de Software Expandir experiencia Replegar experiencia\nLideré integraciones en NestJS y Go y rediseñé flujos entre microservicios, reduciendo aproximadamente un 98% las fallas intermitentes en flujos críticos.\n\nContexto\n\nTrabajé en plataformas de gestión para arrendadoras, flotas y automotrices, dentro del equipo de arquitectura y junto a especialistas de operación y datos.\n\nContribución\n\nLideré integraciones en NestJS y Go y rediseñé flujos entre microservicios, haciendo más predecible el comportamiento ante fallos en los flujos críticos.\n\n[Leer la experiencia completa](/es/experiencias/infosistemas/)\n\n- jul/2025 a dic/2025 Remoto EDS (Policía Civil de Río de Janeiro) Ingeniero Backend — Consultoría Expandir experiencia Replegar experiencia\nEstructuré un sistema de gestión de salud con NestJS para una operación pública crítica y evolucioné rutas backend con foco en seguridad, acceso y trazabilidad.\n\nContexto\n\nLa consultoría cubrió sistemas públicos sensibles, incluyendo un sistema de gestión de salud de alto volumen y un ERP jurídico en evolución.\n\nContribución\n\nEstructuré el backend con NestJS, refactoricé rutas legadas y reforcé la seguridad, el control de acceso y la trazabilidad de flujos sensibles.\n\n[Leer la experiencia completa](/es/experiencias/eds-policia-civil-rio/)",
      "description": "Currículum online de Rafael Pereira, ingeniero backend senior con experiencia en Node.js, Go y Java, y trabajo complementario con React y Angular.",
      "keywords": [
        "experiencia",
        "nestjs",
        "para",
        "ingeniero",
        "node",
        "backend",
        "remoto",
        "expandir",
        "replegar",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/es/curriculum"
      }
    },
    {
      "id": "213126b2adde5a60",
      "url": "https://imrafaeldev.site/casos/mensageria-rabbitmq",
      "title": "Infosistemas: contrato de falha na mensageria RabbitMQ (Part 2)",
      "content": "- retry com backoff para indisponibilidade temporária;\n\n- idempotência e deduplicação no consumidor, porque entrega duplicada não pode repetir efeito de negócio;\n\n- publisher confirms, para reduzir incerteza na publicação;\n\n- ajuste de prefetch, em vez de abrir concorrência indiscriminada.\n\n## Alternativa descartada\n\nTratar o problema como falta de capacidade (mais consumidores, mais prefetch) sem mudar o contrato de falha. Isso moveria o gargalo e manteria perda ou duplicidade silenciosa.\n\n## Implementação\n\nO redesenho colocou esses mecanismos nos fluxos críticos: publicação confirmada, fila durável com prefetch controlado, consumidor idempotente, retry com backoff e DLQ por fluxo. A operação passou a ter um caminho previsível para falha temporária e para falha permanente, em vez de depender de reprocessamento ad hoc.\n\n## Resultado\n\nA redução registrada nas falhas intermitentes dos fluxos críticos entre microsserviços foi de aproximadamente 98%. O número descreve esses fluxos após o redesenho, não a operação inteira da empresa nem outras frentes.\n\n## Limitações\n\nMétricas de outras frentes ainda pendentes de método ou confirmação ficam de fora. Se a volumetria ou o mapa de microsserviços mudar de forma que DLQ e prefetch deixem de isolar a falha, o tuning precisa ser revisto com telemetria de fila e de consumidores.\n\nContato\n\n## Tem um sistema que deixou de ser simples?\n\nChame direto pelo @imrafaeldev, sem formulário — para conversa profissional, comece pelo LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir a página de contato](/contato/)",
      "description": "Redesenho da mensageria RabbitMQ na Infosistemas com filas duráveis, DLQ, retry, idempotência e prefetch. O trabalho reduziu em cerca de 98% as falhas intermitentes entre microsserviços.",
      "keywords": [
        "falha",
        "para",
        "contrato",
        "prefetch",
        "não",
        "mensageria",
        "consumidores",
        "fluxos",
        "imrafaeldev",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/casos/mensageria-rabbitmq"
      }
    },
    {
      "id": "2163d151f4bd8a38",
      "url": "https://imrafaeldev.site/cases/vbet-analytics-pt-br",
      "title": "VBET: analytics sobre um SQL Server que não podíamos mudar (Part 2)",
      "content": "- pior pico inicial: cerca de 7 minutos;\n- após queries e índices: cerca de 3 minutos;\n- após paralelização: cerca de 1 minuto;\n- após ETL/PostgreSQL: cerca de 15 segundos no p99 da comissão sem cache;\n- cache quente: menos de 1 segundo.\n\nCada número pertence a essa etapa. Não descreve o ganho de uma decomposição posterior em microsserviços.\n\n## Limitações\n\nA investigação, a decisão de ETL + base própria, a separação REALTIME/CLOSED e a política de degradação são o núcleo atribuível aqui. A decomposição do monólito em Kubernetes é outra história e não mistura o “500%” nem a latência abaixo de 60 ms com este caso. Versões antigas de currículo que citam 30 segundos em carga fria, 11 segundos ou percentuais de SLA sem cenário ficam de fora. Se o produto passar a exigir precisão realtime no pagamento, a separação CLOSED deixa de ser suficiente.",
      "description": "Dashboard de comissões na VBET: SQL Server externo, ETL próprio e cache. A linha medida foi de cerca de sete minutos no pico até menos de um segundo com cache quente.",
      "keywords": [
        "cerca",
        "não",
        "índices",
        "minutos",
        "realtime",
        "influenciadores",
        "dashboard",
        "comissão",
        "pagamento",
        "base"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "vbet-analytics",
        "slug": "analytics-sql-server-externo",
        "title": "VBET: analytics sobre um SQL Server que não podíamos mudar",
        "description": "Dashboard de comissões na VBET: SQL Server externo, ETL próprio e cache. A linha medida foi de cerca de sete minutos no pico até menos de um segundo com cache quente.",
        "company": "VBET",
        "role": "Engenheiro Backend Sênior",
        "period": "out/2023 a fev/2025",
        "excerpt": "O banco era de outro time. O dashboard precisava deixar de depender de um schema que não controlávamos.",
        "proofValue": "~7 min → <1 s",
        "proofLabel": "comissões, da carga original ao cache quente",
        "featuredClaimId": "vbet-commission-performance",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/vbet-analytics-pt-br.md"
      }
    },
    {
      "id": "233e109202ab1906",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-es",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 1)",
      "content": "La tesis es simple: Node.js con Event Loop y Go con goroutines no resuelven el mismo tipo de problema del mismo modo. La comparación se vuelve mala cuando tratamos ambos como competidores directos en cualquier escenario. En la práctica, el error más común es confundir concurrencia con paralelismo.\n\nConcurrencia es organizar varias tareas que pueden estar en curso al mismo tiempo. Paralelismo es ejecutar trabajo de hecho al mismo tiempo, usando múltiples núcleos de CPU. Esa diferencia parece académica hasta que aparece en producción.\n\nEl Event Loop de Node.js es muy bueno cuando el cuello de botella está en la espera: API externa, base de datos, WebSocket, input de usuario, colas y eventos. Mientras una operación aguarda respuesta, el loop sigue atendiendo otras tareas. Es el dueño de la bodega en el mostrador: no para porque pidió a alguien buscar rapadura en el depósito.\n\nLas goroutines, por otro lado, empiezan a volverse más interesantes cuando el trabajo es CPU bound, divisible y puede aprovechar múltiples núcleos con control explícito de concurrencia. Son unidades ligeras de ejecución gestionadas por el runtime de Go. Con ellas, se puede romper una tarea en partes menores, distribuir la ejecución y sincronizar el resultado al final.\n\nEs más parecido a un puesto lleno en el São João de Caruaru: una persona asa el maíz, otra revuelve la canjica, otra corta el bolo de rolo. El trabajo avanza al mismo tiempo, con cada persona cuidando una parte.\n\n## El punto donde Node.js empieza a sufrir\n\nLo vi de forma muy concreta en un proceso de cálculo de comisión en una casa de apuestas. La aplicación lidiaba con millones de apuestas por día, y parte del flujo implicaba calcular comisión sobre varios lotes de apuestas.\n\nAl principio, el proceso en Node.js funcionaba. Secuencialmente era correcto, pero lento. Cuando intenté paralelizar con la lógica común de varias tareas al mismo tiempo, apareció el límite: el cuello de botella era CPU.",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "más",
        "tiempo",
        "mismo",
        "trabajo",
        "loop",
        "problema",
        "para",
        "resultado"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia",
        "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
        "excerpt": "Node.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-es.md"
      }
    },
    {
      "id": "247ef690d03300f2",
      "url": "https://imrafaeldev.site",
      "title": "Rafael Pereira, engenheiro de software sênior (Part 3)",
      "content": "Orquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\nProduto próprio, desktop\n\n### [md2cv](/projetos/md2cv/)\n\nPerfil e versões imutáveis ficam no SQLite da máquina. O agente supervisionado só propõe alterações sob schema; ATS e exportação em PDF/DOCX reutilizam o mesmo grafo, sem backend SaaS dono dos dados.\n\n[Abrir o projeto](/projetos/md2cv/)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\n[Ver todos os projetos](/projetos/)\n\nPercurso\n\n## Uma trajetória de sistemas, não de cargos\n\n-\n2025 a 2026\n\n### Arquitetura\n\nPlataformas de locação e frotas. A prova pública desta etapa é a mensageria.\n\nInfosistemas\n\n-\n2023 a 2025\n\n### Analytics em base externa\n\nSQL Server de outro time. O dashboard passou a funcionar sem depender de um schema que não controlávamos.\n\nVBET\n\n-\n2023\n\n### Pós-venda sobre legado\n\nO core em PHP 5.7 sustentava a operação. A automação libertou suporte sem reescrever o monólito.\n\nMaxmilhas\n\n-\n2022 a 2023\n\n### Marketplace multi-tenant\n\nWhite-label para incorporar bancos sem fork por cliente.\n\nSouth System / QUIQ-Itaú\n\n-\n2021 a 2022\n\n### Modernização incremental\n\nO monólito não tinha os autores originais. A migração começou pelo que o banco ainda deixava ler.\n\nFlapper\n\nPrática\n\n## Como o trabalho avança\n\nCada etapa restringe a seguinte. Sem essas restrições, arquitetura vira gosto pessoal.\n\nComo o trabalho avança\n\n- Contexto\n- Restrição\n- Decisão\n- Evidência\n- Limite\n\n01\n\n### Contexto\n\nQuem sofre quando isso quebra, e em qual operação.\n\n02\n\n### Restrição\n\nO que não pode parar, mudar ou ser tratado como capacidade extra.\n\n03\n\n### Decisão\n\nO caminho escolhido, com a alternativa descartada à vista.\n\n04\n\n### Evidência\n\nO que foi medido, em qual cenário, com que autoria.\n\n05\n\n### Limite\n\nA condição que justificaria rever a decisão.",
      "description": "Portfólio institucional e hub editorial de Rafael Pereira. Trabalho nos pontos em que sistemas simples deixam de ser simples.",
      "keywords": [
        "não",
        "casos",
        "projetos",
        "abrir",
        "case",
        "decisão",
        "caso",
        "falhas",
        "contato",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/"
      }
    },
    {
      "id": "24c3e0552c965e92",
      "url": "https://imrafaeldev.site/en/articles/derusting-logic-group-anagrams",
      "title": "Derusting logic #01: Group Anagrams (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Derusting logic #01: Group Anagrams\n\n## Derusting logic #01: Group Anagrams\n\nI had not stopped writing code. What changed was outsourcing parts of the reasoning. Group Anagrams was the exercise to recover the habit.\n\nAugust 17, 2026\n\n- [Trade-offs](/en/articles/?topic=trade-offs)\n- [Performance](/en/articles/?topic=performance)\n\nAfter years programming, I realized my logic was rusty. I had not stopped writing code. What changed was that, little by little, I started outsourcing parts of the reasoning I used to exercise alone.\n\nI picked exercise [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/) on LeetCode. The task is to receive a list of strings and group anagrams together.\n\n## What we need to solve\n\nInput:\n\n[\"eat\", \"tea\", \"tan\", \"ate\", \"nat\", \"bat\"]\nPossible result:\n\n[\"eat\", \"tea\", \"ate\"]\n[\"tan\", \"nat\"]\n[\"bat\"]\nGroup order does not matter.\n\n## What is an anagram?\n\nTake eat, tea, and ate. Each has a once, e once, and t once. Positions change; the count of each letter stays the same.\n\nThe algorithm must turn those words into a shared representation. If all three produce the same key, I can use that key in a map and place them in the same group. The first problem is creating that key.\n\n## My first answer was sorting\n\nI started using the sorted string as the key:\n\neat → aet\ntea → aet\nate → aet\nAll three produce aet.\n\n### Sort-based solution\n\nThat was the first solution that came to mind. I was not trying for the leanest implementation right away. I wanted a coherent solution and to understand where it could improve.\n\nfunc sortString(str string) string {\nb := []byte(str)\nslices.Sort(b)\nreturn string(b)\n}\n\nfunc groupAnagrams(strs []string) [][]string {\nmapping := make(map[string][]string)\n\nfor _, str := range strs {\nsortedStr := sortString(str)\nmapping[sortedStr] = append(mapping[sortedStr], str)\n}\n\nresult := make([][]string, 0, len(mapping))\n\nfor _, group := range mapping {\nresult = append(result, group)\n}",
      "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
      "keywords": [
        "string",
        "group",
        "that",
        "result",
        "anagrams",
        "same",
        "solution",
        "what",
        "each",
        "problem"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/en/articles/derusting-logic-group-anagrams"
      }
    },
    {
      "id": "25bec0c11d6f1bd7",
      "url": "https://imrafaeldev.site/artigos/backend-performance-perto-dos-dados",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- A maioria dos problemas de performance de backend começa perto dos dados\n\n## A maioria dos problemas de performance de backend começa perto dos dados\n\nAPI lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.\n\n16 de julho de 2026\n\n- [Performance](/artigos/?topic=performance)\n- [Arquitetura](/artigos/?topic=arquitetura)\n\nUma API fica lenta e a reunião já enche de soluções antes de ganhar uma medição. Aparecem mais CPU e RAM, cache, fila, microserviços e refatoração. Às vezes alguém propõe até trocar a linguagem. Raramente a primeira sugestão é abrir o plano de execução.\n\nPor isso, começo a investigação perto dos dados. Não porque o banco seja o culpado por padrão, mas porque muita coisa passa por ali e essa verificação costuma dar sinal rápido. Se a consulta estiver saudável, tiro o banco da frente e sigo o fluxo da requisição.\n\n## Soluções rápidas também escondem trabalho caro\n\nCache e fila resolvem problemas reais. Mais máquina também pode ser a decisão certa. Quando entram por reflexo, porém, esses recursos podem apenas mudar o tamanho da conta enquanto ninguém sabe qual trecho da requisição está segurando a resposta.\n\nSubir CPU reduz a disputa por recurso, cache tira algumas requisições do caminho e fila absorve um pico. Enquanto isso, uma consulta que faz um trabalho enorme para retornar quase nada continua cara em cada execução. O mesmo vale para um loop que dispara uma chamada por item.\n\nA latência pode cair por um tempo, o alerta para de tocar e o time respira. Quando a carga cresce, a operação cara reaparece, agora acompanhada de uma infraestrutura maior.\n\nA pergunta que precisa vir antes da discussão de arquitetura: quanto trabalho essa requisição está produzindo para entregar o resultado?\n\n## Quase dobramos a máquina e o dashboard continuou lento",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "antes",
        "execução",
        "dados",
        "não",
        "trabalho"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/artigos/backend-performance-perto-dos-dados"
      }
    },
    {
      "id": "25f85826d7227649",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 5)",
      "content": "```typescript\nexport const mockDbConnector: DbConnector = {\n  users: {\n    getById: async (id: string) =>\n      Promise.resolve(new UserEntity({ id, name: \"Test\" })),\n    getByName: async (name: string) =>\n      Promise.resolve(new UserEntity({ id: \"1\", name })),\n    register: async (name: string) =>\n      Promise.resolve(new UserEntity({ id: \"2\", name })),\n    update: async (id: string, name: string) =>\n      Promise.resolve(new UserEntity({ id, name })),\n    delete: async (_id: string) => Promise.resolve(),\n  },\n  profiles: {\n    getById: async (_id: string) => Promise.resolve(null),\n    getByName: async (_name: string) => Promise.resolve(null),\n    register: async (_name: string) => Promise.resolve(null),\n    update: async (_id: string, _name: string) => Promise.resolve(null),\n    delete: async (_id: string) => Promise.resolve(),\n  },\n};\n```\n\n`UserEntity` (Core) is not the database table. They are different things.\n\n**\"Does implementing several protocols violate the S in SOLID?\"** It depends. Splitting each protocol into its own class maximizes SRP. Keeping one repository per entity/aggregate is also coherent: the scope is \"User data in this connector\". Practical criteria for *not* joining A and B in the same class:\n\n1. Implementing B would require another external dependency.\n2. A and B work on different entities/scopes.\n3. Cyclomatic (or cognitive) complexity rises too much.\n4. The class passes ~500 lines — subjective threshold; use team judgment.\n\nWith DI and interface segregation, `CreateUserUsecase` only knows `CreateUserProtocol` and `GetUserByNameProtocol`. At runtime it may receive the same `UsersMockRepository` instance in both parameters — without coupling to the concrete class.\n\n### Services\n\nServices adapt Infra calls into Core: REST → service → usecase → protocol → repository → database. The same design holds for a queue handler or GraphQL resolver: different input, service in the middle.",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "2600cc3ec75858cf",
      "url": "https://imrafaeldev.site/en/articles/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 2)",
      "content": "The first step is the strategy contract interface. For the calculator, something receiving two numbers and returning the result:\n\ninterface BinaryOperationParameters {\nfirstOperand: number;\nsecondOperand: number;\noperator: string;\n}\n\ntype Result = number;\n\ninterface BinaryOperationStrategy {\ncalculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result;\n}\n\n## Implementing concrete strategies\n\nEach math operation becomes a class implementing BinaryOperationStrategy and executing a single operation. Addition and division:\n\nclass Sum implements BinaryOperationStrategy {\npublic calculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result {\nconst { firstOperand, secondOperand } = parameters;\nreturn firstOperand + secondOperand;\n}\n}\n\nclass Division implements BinaryOperationStrategy {\npublic calculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result {\nconst { firstOperand, secondOperand } = parameters;\nif (secondOperand === 0) {\nthrow new Error(\"Division by zero is not allowed!\");\n}\nreturn firstOperand / secondOperand;\n}\n}\n\n## The Context and the Factory\n\nTo tie strategies together, a Context and a Factory (or Analyzer) come in. In ContextAnalyzer, a method evaluates the operator and returns the right Strategy:\n\nclass ContextAnalyzer {\npublic getInstance(operator: string): BinaryOperationStrategy {\nswitch (operator) {\ncase \"*\":\nreturn new Multiplication();\ncase \"+\":\nreturn new Sum();\ncase \"-\":\nreturn new Subtraction();\ncase \"/\":\nreturn new Division();\ncase \"%\":\nreturn new Percent();\ncase \"**\":\nreturn new Pow();\ndefault:\nthrow new Error(\"Operator not found!\");\n}\n}\n}\nThe context receives and executes the strategy. It knows only the contract, not the concrete implementation. The old DefaultCalculator starts receiving this ContextAnalyzer by injection:\n\nclass Calculator {\nconstructor(private readonly contextAnalyzer: ContextAnalyzer) {}",
      "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "strategy",
        "return",
        "case",
        "class",
        "operator",
        "parameters",
        "each",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/en/articles/design-patterns-strategy"
      }
    },
    {
      "id": "2700f28f4bff32c6",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-es",
      "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter (Part 3)",
      "content": "*Publicado originalmente en [LinkedIn](https://www.linkedin.com/pulse/pare-de-ser-ref%C3%A9m-das-depend%C3%AAncias-diga-bem-vindo-ao-design-rafael/) el 26 de abril de 2022.*",
      "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
      "keywords": [
        "adapter",
        "regla",
        "negocio",
        "base",
        "servicio",
        "readonly",
        "contrato",
        "createdatabasecustomerprotocol",
        "customerinputentity",
        "successfulentitycreation"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter",
        "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
        "excerpt": "Migrar de MySQL a PostgreSQL no exige reescribir el servicio. El Adapter aísla el plugin tras un contrato que la regla de negocio entiende.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-es.md"
      }
    },
    {
      "id": "271d871cd84f87f2",
      "url": "https://imrafaeldev.site/es/experiencias/azify",
      "title": "Azify",
      "content": "- [Inicio](/es/)\n- Azify\n\n## Azify\n\nMi consultoría en Azify con liquidación, BaaS y servicios financieros.\n\nCargo\n\nIngeniero Backend Senior — Consultoría\n\nPeríodo\n\nmar/2025 a jun/2025\n\n## Contexto\n\nEn Azify, trabajé como consultor en infraestructura financiera para fintechs y bancos pequeños. El producto reunía capacidades como Pix, transferencias, tarjetas, billetera digital y otros servicios bancarios.\n\n## Cómo trabajé\n\nParticipé en decisiones arquitectónicas y ayudé a establecer prácticas de desarrollo para un entorno donde consistencia, seguridad y estabilidad tenían impacto financiero directo. Desarrollé un motor de liquidación en NestJS con integraciones multi-exchange, monitoreo de riesgo y controles de compliance.\n\nTambién trabajé en integraciones de exchanges y blockchains para flujos transaccionales y en una plataforma BaaS multi-tenant con OAuth 2.0, JWT y cifrado. Mediante profiling de consultas, revisión de índices y optimización de Redis, reduje en 30% la latencia de APIs financieras críticas.\n\n## Lo que me llevé\n\nEsta consultoría retomó partes recurrentes de mi trayectoria, como pagos, multi-tenancy y sistemas transaccionales, en un contexto con mayor responsabilidad sobre autorización y consistencia.",
      "description": "Mi consultoría en Azify con liquidación, BaaS y servicios financieros.",
      "keywords": [
        "azify",
        "consultoría",
        "trabajé",
        "como",
        "para",
        "2025",
        "liquidación",
        "baas",
        "servicios",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/azify"
      }
    },
    {
      "id": "27243cd1288bc755",
      "url": "https://imrafaeldev.site/projetos/md2cv",
      "title": "md2cv (Part 2)",
      "content": "- ATS: diagnósticos estruturais e pontuação orientativa; PDF textual marcado e DOCX semântico.\n\n- Candidaturas: empresa, vaga, currículo base, versão e estado no mesmo contexto.\n\n- Agentes: máquina de estados para perguntas, tentativas e proposta de nova versão sob schema; só persiste com confirmação.\n\n- Dados: importação e exportação versionada do grafo completo; compilador Markdown (unified/remark) compartilhado por preview, auditoria e export.\n\nFluxo canônico: perfil → currículo base → versão imutável → auditoria ATS → PDF/DOCX. O ramo de candidatura passa por agente local supervisionado antes do currículo adaptado.\n\n## Estado atual\n\nRepositório público sob MIT, portal de documentação e releases x64 para Linux (AppImage, .deb, .rpm) e Windows (NSIS e portátil), com CI e testes Vitest e Playwright em persistência, agentes, ATS e fluxos Electron.\n\nO export profissional usado neste site nasce desse produto e não é lido pelo site em runtime.\n\n## Limitações\n\nNão substitui revisão humana nem promete contratação ou aprovação automática por plataformas de recrutamento. A adaptação a vagas depende de respostas confirmadas; o sistema recusa fabricar experiência.\n\nBinários Windows desta fase não possuem assinatura de código. O projeto é autoral e sem finalidade comercial do mantenedor; a licença MIT permite reutilização nos seus termos.",
      "description": "Estúdio desktop local-first para perfil profissional, currículos Markdown, versões imutáveis, ATS e adaptação a vagas com agentes supervisionados. Os dados ficam no SQLite da máquina.",
      "keywords": [
        "grafo",
        "não",
        "perfil",
        "produto",
        "agente",
        "só",
        "candidatura",
        "currículo",
        "versão",
        "projetos"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/projetos/md2cv"
      }
    },
    {
      "id": "28e040a8294bd0f2",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 6)",
      "content": "If the plan is healthy and fifty 40 ms calls add up to about two seconds, I stop hunting indexes and measure the code. If reads explode before aggregation, the code waits while I test the query.\n\nThat window is an investigation ruler, not a guarantee. On the slow dashboard, I spent about three hours in the terminal looking at the plan, comparing metrics, and testing the query split. The rest of the day involved meetings, access, deploy, and production confirmation.\n\nSo on day one I want at least one tested hypothesis with before-and-after measurement. The test may rewrite the filter without `CAST`, split the query, or bound the loop's concurrency. After deploy, I compare the metric that motivated the change: reads and CPU for the database; latency, errors, and saturation for the route.\n\nWith intermittent failures, that window grows. You must wait for the symptom to reappear with enough metrics to compare.\n\n## Confirmation happens after deploy\n\nAfter some production mistakes, I stopped looking for a default culprit. Database, application, and infrastructure enter as hypotheses, each with a measure that can confirm or discard it.\n\nWhen the plan shows high reads and cardinality far from reality, I work on the query. When the query answers well and profiling concentrates time after it, I move to code or integration.\n\nCache, queues, indexes, and bigger machines stay available. They are not forbidden decisions. They just should not arrive before we know which work is holding the response.\n\nBefore changing, I define the measure that must improve and the one that must not worsen. After deploy, I return to the plan, metrics, and profiling. If the target metric does not improve, the hypothesis did not hold. If the guardrail measure worsens, the fix created another problem.\n\nI discard the hypothesis and return to the flow point where time still grows.\n\n---",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "2a421e9d36360465",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 7)",
      "content": "type Message struct {\n\tID  string // chave de idempotência (simulada)\n\tKey string // rótulo didático: não demonstra particionamento\n\tBody string\n}\n\n// Store é simulação sequencial e volátil: perde o estado ao reiniciar.\ntype Store struct {\n\tmu   sync.Mutex\n\tdone map[string]bool\n}\n\nfunc (s *Store) AlreadyProcessed(id string) bool {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\treturn s.done[id]\n}\n\nfunc (s *Store) MarkProcessed(id string) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\ts.done[id] = true\n}\n\nfunc handle(m Message) error {\n\tif m.Body == \"poison\" {\n\t\treturn errors.New(\"falha persistente\")\n\t}\n\tfmt.Println(\"processada:\", m.ID)\n\treturn nil\n}\n\nfunc consume(messages []Message, store *Store) {\n\tvar dlq []Message // simulação volátil: perde o conteúdo ao reiniciar\n\tfor _, m := range messages { // processamento sequencial: sem concorrência nem partição por chave\n\t\tif store.AlreadyProcessed(m.ID) {\n\t\t\tcontinue // reentrega simulada\n\t\t}\n\t\tvar err error\n\t\tfor attempt := 1; attempt <= 3; attempt++ {\n\t\t\tif err = handle(m); err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttime.Sleep(time.Duration(attempt) * 50 * time.Millisecond)\n\t\t}\n\t\tif err != nil {\n\t\t\tdlq = append(dlq, m) // simula mover para DLQ e confirmar, sem durabilidade\n\t\t\tcontinue\n\t\t}\n\t\tstore.MarkProcessed(m.ID) // ACK lógico simulado, sem efeito durável\n\t}\n\tfmt.Println(\"dlq:\", len(dlq))\n}\n\nfunc main() {\n\tstore := &Store{done: map[string]bool{}}\n\tmsgs := []Message{\n\t\t{ID: \"1\", Key: \"pedido-7\", Body: \"ok\"},\n\t\t{ID: \"1\", Key: \"pedido-7\", Body: \"ok\"}, // reentrega\n\t\t{ID: \"2\", Key: \"pedido-7\", Body: \"poison\"},\n\t}\n\tconsume(msgs, store)\n}\n```",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "2b469a31f99bc525",
      "url": "https://imrafaeldev.site/en/articles/derusting-logic-container-with-most-water",
      "title": "Derusting logic #02: Container With Most Water (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Derusting logic #02: Container With Most Water\n\n## Derusting logic #02: Container With Most Water\n\nI tried to pick the next step by looking only at the neighbors. It worked in some cases, but the problem asked for a wider view.\n\nSeptember 15, 2026\n\n- [Performance](/en/articles/?topic=performance)\n- [Trade-offs](/en/articles/?topic=trade-offs)\n\nAfter solving the first exercise of the series, I stayed on LeetCode to work on logic without asking for a ready-made solution. Challenge 011 is [Container With Most Water](https://leetcode.com/problems/container-with-most-water/).\n\nWe get an array of heights and need to pick two lines that form the container with the largest area.\n\n## How to compute the area\n\nIf I pick positions left and right, the width is the distance between them. The container height is limited by the shorter of the two lines.\n\narea = min(left height, right height) × distance\nFor example, with these heights:\n\n[1, 8, 6, 2, 5, 4, 8, 3, 7]\nThe lines at positions 1 and 8 have heights 8 and 7. The shorter height is 7, and the distance between them is 7. That combination produces an area of 49.\n\n## My first attempt\n\nI started with two pointers, one at each end of the array. After computing the current area, I simulated two possibilities:\n\n- advancing the left pointer;\n\n- moving the right pointer back.\n\nI computed the area of the next two pairs and picked the larger one. The main excerpt was this:\n\nconst paddingLeftArea =\nMath.min(heights[leftIndex + 1], heights[rigthIndex]) *\n(rigthIndex - leftIndex + 1);\n\nconst paddingRightArea =\nMath.min(heights[leftIndex], heights[rigthIndex - 1]) *\n(rigthIndex - 1 - leftIndex);",
      "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
      "keywords": [
        "heights",
        "area",
        "leftindex",
        "rigthindex",
        "that",
        "height",
        "container",
        "with",
        "maxarea",
        "problem"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/en/articles/derusting-logic-container-with-most-water"
      }
    },
    {
      "id": "2b52c6d863ba4af1",
      "url": "https://imrafaeldev.site/cases/vbet-analytics-en",
      "title": "VBET: analytics on top of a SQL Server we could not change (Part 1)",
      "content": "## Context\n\nAt VBET, between October 2023 and February 2025, the analytics product served iGaming influencers and affiliates. The dashboard gathered dozens of metrics; commission was the most critical reading. Influencers accepted a small lag in same-day data as long as the screen responded. Payout depended on consolidated prior-day data, not on the live value.\n\n## Constraints\n\nThe SQL Server was external, shared, and not reliably modifiable. Temporary indexes could be removed by the database owner. The original API mixed SQL queries built from parameters, with injection risk, and aggregated too much in memory.\n\n## Problem\n\nThe system had been sized for smaller influencers. With larger bases, the worst dashboard peak reached about seven minutes. Security and maintainability came before performance: raw queries, little test coverage, and a synchronous path that recalculated too much on every request.\n\n## Decision\n\nThe evolution was incremental, in the order the constraints appeared:\n\n1. remove unsafe SQL, parameterize access, document, and test;\n2. optimize queries and temporary indexes, as mitigation rather than invariant;\n3. parallelize independent queries with Go, goroutines, and channels;\n4. when the bottleneck moved back to SQL Server, build an owned ETL and PostgreSQL, with pre-computation, checkpoints, and reconciliation;\n5. separate REALTIME reads (trend, eventual consistency) from CLOSED reads (financial accuracy and payout);\n6. cache-aside with TTL aligned to the accepted lag of about five minutes;\n7. controlled degradation if the cache failed, instead of taking the screen down.\n\n## Discarded alternative\n\nInsisting on indexes in the external database as architecture, or recalculating years of history on every access. Also discarded: paying affiliates from REALTIME data.\n\n## Result\n\nThe reconciled line in the experience dossier, for the commission/dashboard path, is:",
      "description": "Commission dashboard at VBET: external SQL Server, owned ETL, and cache. The measured line went from about seven minutes at peak to under one second with a warm cache.",
      "keywords": [
        "about",
        "queries",
        "with",
        "indexes",
        "minutes",
        "realtime",
        "influencers",
        "dashboard",
        "commission",
        "data"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "vbet-analytics",
        "slug": "external-sql-server-analytics",
        "title": "VBET: analytics on top of a SQL Server we could not change",
        "description": "Commission dashboard at VBET: external SQL Server, owned ETL, and cache. The measured line went from about seven minutes at peak to under one second with a warm cache.",
        "company": "VBET",
        "role": "Senior Backend Engineer",
        "period": "Oct/2023 – Feb/2025",
        "excerpt": "The database belonged to another team. The dashboard had to stop depending on a schema we did not control.",
        "proofValue": "~7 min → <1 s",
        "proofLabel": "commissions, from the original load to the warm cache",
        "featuredClaimId": "vbet-commission-performance",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/vbet-analytics-en.md"
      }
    },
    {
      "id": "2b6aa0cedc787ca4",
      "url": "https://imrafaeldev.site/artigos",
      "title": "Artigos (Part 2)",
      "content": "[Performance](/artigos/?topic=performance)\n- [Arquitetura](/artigos/?topic=arquitetura)\nAPI lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.\n\n-\n\n## [Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência](/artigos/goroutines-vs-event-loop/)\n\n24 de junho de 2026\n\n[Trade-offs](/artigos/?topic=trade-offs)\n- [Performance](/artigos/?topic=performance)\nNode.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.\n\n-\n\n## [TypeScript Clean Architecture: Core, Adapters e Infra](/artigos/typescript-cleanarch/)\n\n15 de março de 2023\n\n[Arquitetura](/artigos/?topic=arquitetura)\nArquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.\n\n-\n\n## [Design Patterns: Strategy](/artigos/design-patterns-strategy/)\n\n5 de maio de 2022\n\n[Arquitetura](/artigos/?topic=arquitetura)\nCadeias de if/else crescem e ficam frágeis. O Strategy isola cada algoritmo atrás de um contrato.\n\n-\n\n## [Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter](/artigos/design-patterns-adapter/)\n\n26 de abril de 2022\n\n[Arquitetura](/artigos/?topic=arquitetura)\nMigrar de MySQL para PostgreSQL não precisa reescrever o serviço. O Adapter isola o plugin atrás de um contrato que a regra de negócio entende.",
      "description": "Padrões e decisões de engenharia em prosa técnica, com código.",
      "keywords": [
        "artigos",
        "topic",
        "arquitetura",
        "performance",
        "trade-offs",
        "2026",
        "para",
        "concorrência",
        "não",
        "setembro"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/artigos"
      }
    },
    {
      "id": "2db03f424b4faa3a",
      "url": "https://imrafaeldev.site/en/articles",
      "title": "Articles (Part 1)",
      "content": "- [Home](/en/)\n- Articles\n\n## Articles\n\nEngineering patterns and decisions in technical prose, with code.\n\nAllPerformanceArchitectureTrade-offsMessaging\n\n-\n\n## [Go intensive: concurrency, resilience, and distributed systems in 30 minutes](/en/articles/go-intensive/)\n\nSeptember 22, 2026\n\n[Architecture](/en/articles/?topic=arquitetura)\n- [Performance](/en/articles/?topic=performance)\n- [Messaging](/en/articles/?topic=mensageria)\nA 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.\n\n-\n\n## [Derusting logic #02: Container With Most Water](/en/articles/derusting-logic-container-with-most-water/)\n\nSeptember 15, 2026\n\n[Performance](/en/articles/?topic=performance)\n- [Trade-offs](/en/articles/?topic=trade-offs)\nI tried to pick the next step by looking only at the neighbors. It worked in some cases, but the problem asked for a wider view.\n\n-\n\n## [Derusting logic #01: Group Anagrams](/en/articles/derusting-logic-group-anagrams/)\n\nAugust 17, 2026\n\n[Trade-offs](/en/articles/?topic=trade-offs)\n- [Performance](/en/articles/?topic=performance)\nI had not stopped writing code. What changed was outsourcing parts of the reasoning. Group Anagrams was the exercise to recover the habit.\n\n-\n\n## [Most backend performance problems start close to the data](/en/articles/backend-performance-close-to-data/)\n\nJuly 16, 2026\n\n[Performance](/en/articles/?topic=performance)\n- [Architecture](/en/articles/?topic=arquitetura)\nSlow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.\n\n-\n\n## [Goroutines vs Event Loop: the wrong comparison between two concurrency models](/en/articles/goroutines-vs-event-loop/)\n\nJune 24, 2026",
      "description": "Engineering patterns and decisions in technical prose, with code.",
      "keywords": [
        "articles",
        "topic",
        "performance",
        "architecture",
        "with",
        "trade-offs",
        "2026",
        "arquitetura",
        "concurrency",
        "2022"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/articles"
      }
    },
    {
      "id": "2e95ed0b9adc9970",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 3)",
      "content": "Cuidado: un usecase que solo delega al protocol sin validar puede estar empujando regla de negocio hacia el adapter. En `CreateUserUsecase`, la verificación de nombre duplicado es obligación del Core.\n\n### Exceptions\n\n`UserAlreadyExistsException` pertenece al Core: el flujo inválido de la regla también es regla. Cada fallo mapeado a una excepción conocida ayuda al mantenimiento. Base con `code` (después se vuelve status HTTP en el borde):\n\n```typescript\n// core/exceptions/IBaseException.ts\nexport abstract class IBaseException extends Error {\n  code: number;\n\n  constructor(message: string) {\n    super(message);\n  }\n}\n```\n\n```typescript\n// core/exceptions/UserAlreadyExistsException.ts\nexport class UserAlreadyExistsException extends IBaseException {\n  constructor(message?: string) {\n    super(message ?? \"User already exists\");\n    this.code = 400;\n  }\n}\n```\n\nEl usecase **lanza** excepciones; **no** las trata. Mapear tipo desconocido → tipo conocido queda en adapter o infra.\n\n### Protocols\n\n`CreateUserProtocol` y `GetUserByNameProtocol` son contratos de acceso a dispositivo externo. El protocol existe para informar o disparar acción externa — **no** para procesar regla de negocio. Preferencia: un método público por protocol.\n\n```typescript\n// core/protocols/CreateUserProtocol.ts\nexport abstract class CreateUserProtocol {\n  abstract register(name: string): Promise<UserEntity>;\n}\n```\n\n```typescript\n// core/protocols/GetUserByNameProtocol.ts\nexport abstract class GetUserByNameProtocol {\n  abstract getByName(name: string): Promise<UserEntity | null>;\n}\n```\n\nEl Core es el centro; la capa de adaptación conecta el resto.\n\n## Adapter\n\nLos adapters controlan el tráfico bidireccional: externo → regla y regla → externo. Adaptan objetos, parámetros y excepciones — el mismo espíritu del [patrón Adapter](/es/articulos/design-patterns-adapter/).\n\nDos grupos:\n\n1. Llamados por el Core — implementan al menos un protocol.\n2. Llamados por la Infra — en general **services**.",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "2e9b6678167c9f60",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 8)",
      "content": "// core/usecases/GetUserUsecase.ts\nexport class GetUserUsecase implements GetUser {\n  constructor(private readonly getProtocol: GetUserByIdProtocol) {}\n\n  async execute(id: string): Promise<UserEntity> {\n    return this.getProtocol.getById(id);\n  }\n}\n```\n\n### Update\n\n```typescript\n// core/features/UpdateUser.ts\nexport abstract class UpdateUser {\n  abstract execute(id: string, name: string): Promise<UserEntity>;\n}\n\n// core/usecases/UpdateUserUsecase.ts\nexport class UpdateUserUsecase implements UpdateUser {\n  constructor(\n    private readonly updateProtocol: UpdateUserProtocol,\n    private readonly getByIdProtocol: GetUserByIdProtocol,\n  ) {}\n\n  async execute(id: string, name: string): Promise<UserEntity> {\n    const exists = await this.getByIdProtocol.getById(id);\n\n    if (!exists) {\n      throw new UserNotExistsException(`User with id ${id} not exists`);\n    }\n\n    return this.updateProtocol.update(id, name);\n  }\n}\n```\n\n### Delete\n\n```typescript\n// core/features/DeleteUser.ts\nexport abstract class DeleteUser {\n  abstract execute(id: string): Promise<void>;\n}\n\n// core/usecases/DeleteUserUsecase.ts\nexport class DeleteUserUsecase implements DeleteUser {\n  constructor(\n    private readonly deleteProtocol: DeleteUserProtocol,\n    private readonly getByIdProtocol: GetUserByIdProtocol,\n  ) {}\n\n  async execute(id: string): Promise<void> {\n    const exists = await this.getByIdProtocol.getById(id);\n\n    if (!exists) {\n      throw new UserNotExistsException(`User with id ${id} not exists`);\n    }\n\n    return this.deleteProtocol.delete(id);\n  }\n}\n```\n\n### Remaining exception and protocols\n\n```typescript\nexport class UserNotExistsException extends IBaseException {\n  constructor(message: string) {\n    super(message);\n    this.code = 404;\n  }\n}\n```\n\n```typescript\n// core/protocols/GetUserByIdProtocol.ts\nexport abstract class GetUserByIdProtocol {\n  abstract getById(id: string): Promise<UserEntity>;\n}",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 7,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "2ef95f8b72ecca94",
      "url": "https://imrafaeldev.site/experiences/braistech-en",
      "title": "Braistech | Rafael Pereira",
      "content": "## Context\n\nAt Braistech, I had one of my first product experiences in a small environment with a distributed level of responsibility. The domain involved crypto-asset contracts and financial movements.\n\n## How I worked\n\nI led the structure of the main system with Node.js and NestJS, took part in designing microservices for the business core, and developed Flutter applications. I also built a contract system and worked on payment integrations related to the Binance ecosystem.\n\nI participated in a transition from an MVC organization toward Clean Architecture. The goal was to reduce coupling and make a growing system easier to maintain, while I guided junior developers through the code decisions.\n\n## What I took from it\n\nThis stage consolidated my interest in backend and architecture. Working across the full product also gave me a full-stack perspective that remains useful in conversations with frontend and product teams.",
      "description": "My experience at Braistech with product, microservices, and crypto assets.",
      "keywords": [
        "product",
        "with",
        "system",
        "worked",
        "took",
        "also",
        "from",
        "architecture",
        "context",
        "braistech"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-braistech",
        "slug": "braistech",
        "title": "Braistech | Rafael Pereira",
        "description": "My experience at Braistech with product, microservices, and crypto assets.",
        "company": "Braistech",
        "role": "Mid-level Full Stack Software Engineer",
        "period": "Nov 2019 to Jan 2021",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/braistech-en.md"
      }
    },
    {
      "id": "2fb1f3c846d83f77",
      "url": "https://imrafaeldev.site/curriculo",
      "title": "Currículo (Part 3)",
      "content": "Desenvolvi microsserviços em Node.js e NestJS e automatizei regras, cálculos e validações de pós-venda; a mudança reduziu em 34% a necessidade de intervenção manual do suporte.\n\n[Ler experiência completa](/experiencias/maxmilhas/)\n\n- jun/2022 a abr/2023 Remoto South System (alocado na QUIQ/Itaú) Engenheiro Backend Expandir experiência Recolher experiência\nArquitetei um marketplace multi-tenant com Node.js e serviços assíncronos em Go, estruturando testes críticos com aproximadamente 95% de cobertura.\n\nContexto\n\nO trabalho era voltado a um marketplace white-label e multi-tenant para instituições financeiras, preparado para incorporar novos bancos sem forks por cliente.\n\nContribuição\n\nParticipei da arquitetura e do refinamento de regras, usando Node.js e serviços assíncronos em Go para isolar variações por tenant; estruturei testes críticos com aproximadamente 95% de cobertura.\n\n[Ler experiência completa](/experiencias/south-system-quiq-itau/)\n\n- set/2021 a jun/2022 Remoto Flapper Engenheiro de Software Full Stack Expandir experiência Recolher experiência\nMigrei módulos de pessoas, autenticação e aeronaves para serviços em Node.js, NestJS e Go; a separação por domínios reduziu em aproximadamente 25% o número de tabelas.\n\nContexto\n\nA plataforma de aviação executiva dependia de um monólito com mais de sete anos, pouca documentação e regras difíceis de separar sem interromper a operação.\n\nContribuição\n\nMapeei fronteiras de domínio e conduzi uma modernização incremental, migrando módulos para serviços em Node.js, NestJS e Go; a separação reduziu em aproximadamente 25% o número de tabelas.\n\n[Ler experiência completa](/experiencias/flapper/)\n\n- jan/2021 a ago/2021 Remoto Sustentec Engenheiro de Software Full Stack Pleno Expandir experiência Recolher experiência\nMantive e evoluí um sistema de gestão de laboratórios em Java, Spring Boot e Angular, implementando testes de integração antes inexistentes.\n\nContexto",
      "description": "Currículo online de Rafael Pereira, engenheiro backend sênior com experiência em Node.js, Go e Java, e atuação complementar em React e Angular.",
      "keywords": [
        "experiência",
        "para",
        "nestjs",
        "engenheiro",
        "node",
        "backend",
        "remoto",
        "expandir",
        "recolher",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/curriculo"
      }
    },
    {
      "id": "3026da41f4e634ec",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 4)",
      "content": "Después de comparar las versiones del filtro, leo el plan como una historia del trabajo producido por la consulta. Empiezo por el tiempo total y por las lecturas. Si la pantalla devuelve diez indicadores, pero la ejecución hace cientos de miles de lecturas, hay una cuenta por explicar.\n\nDespués comparo la cardinalidad estimada con la real. Cuando el optimizador esperaba pocas filas y recibió muchas, puede haber elegido joins, memoria y agregaciones para un escenario bien menor que el encontrado durante la ejecución.\n\nEnseguida acompaño dónde crecen los datos. Busco el operador en que un JOIN lleva miles de filas a cientos de miles, poco antes de que un filtro o `GROUP BY` lo reduzca todo de nuevo. La respuesta final pequeña puede esconder un volumen intermedio enorme. Es en ese camino donde agregaciones y ordenaciones empiezan a gastar demasiada CPU.\n\nTampoco tomo el porcentaje de costo exhibido por el plan como sentencia. Sirve para elegir dónde medir. Marco el operador caro, cuántas filas entran y salen de él y cuánto tiempo consume.\n\nSi el costo aparece después de la multiplicación de las filas y antes de los pocos indicadores necesarios, ya existe una hipótesis concreta para probar.\n\n## Romper la consulta donde crece el costo\n\nLo que resolvió el dashboard fue romper la consulta y aprovechar los índices correctamente. La división no ocurrió de forma arbitraria. El propio plan mostró dónde el costo empezaba a crecer.\n\nMantuvimos en la base los filtros y la búsqueda indexada de los registros necesarios. Llevar ese recorte a la aplicación haría que el servidor recibiera un conjunto mayor antes de poder descartarlo. También desperdiciaría el camino que los índices ya acortaban.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "32e76536118f910e",
      "url": "https://imrafaeldev.site/es/casos/analitica-sql-server-externo",
      "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar (Part 3)",
      "content": "[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir la página de contacto](/es/contacto/)",
      "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
      "keywords": [
        "cerca",
        "imrafaeldev",
        "vbet",
        "server",
        "base",
        "dashboard",
        "caché",
        "cada",
        "índices",
        "minutos"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/es/casos/analitica-sql-server-externo"
      }
    },
    {
      "id": "32f3df019facbf26",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-group-anagrams",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- Desenoxidando la lógica #01: Group Anagrams\n\n## Desenoxidando la lógica #01: Group Anagrams\n\nYo no había dejado de escribir código. Lo que cambió fue tercerizar partes del razonamiento. Group Anagrams fue el ejercicio para recuperar el hábito.\n\n17 de agosto de 2026\n\n- [Trade-offs](/es/articulos/?topic=trade-offs)\n- [Rendimiento](/es/articulos/?topic=performance)\n\nDespués de años programando, percibí que mi lógica estaba oxidada. Yo no había dejado de escribir código. Lo que cambió fue que, poco a poco, empecé a tercerizar partes del razonamiento que antes necesitaba ejercitar solo.\n\nElegí el ejercicio [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/) en LeetCode. La propuesta es recibir una lista de strings y reunir los anagramas en el mismo grupo.\n\n## Lo que necesitamos resolver\n\nEntrada:\n\n[\"eat\", \"tea\", \"tan\", \"ate\", \"nat\", \"bat\"]\nResultado posible:\n\n[\"eat\", \"tea\", \"ate\"]\n[\"tan\", \"nat\"]\n[\"bat\"]\nEl orden de los grupos no importa.\n\n## ¿Qué es un anagrama?\n\nToma eat, tea y ate. Cada una tiene a una vez, e una vez y t una vez. La posición cambia; la cantidad de cada letra sigue igual.\n\nEl algoritmo necesita transformar esas palabras en una representación común. Si las tres producen la misma clave, puedo usar esa clave en un map y colocarlas en el mismo grupo. El primer problema es crear esa clave.\n\n## Mi primera respuesta fue ordenar\n\nEmpecé usando el string ordenado como clave:\n\neat → aet\ntea → aet\nate → aet\nLas tres producen aet.\n\n### Solución usando sort\n\nEsta fue la primera solución que se me ocurrió. No intentaba la implementación más concisa de inmediato. Quería montar una solución coherente y entender dónde podría mejorar.\n\nfunc sortString(str string) string {\nb := []byte(str)\nslices.Sort(b)\nreturn string(b)\n}\n\nfunc groupAnagrams(strs []string) [][]string {\nmapping := make(map[string][]string)",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "group",
        "clave",
        "solución",
        "result",
        "problema",
        "sort",
        "groups"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/es/articulos/desenoxidando-logica-group-anagrams"
      }
    },
    {
      "id": "334fc252ebf3a540",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 3)",
      "content": "Ninguna de estas señales cierra el diagnóstico. Un N+1 en una ruta con dos ítems puede tener impacto irrelevante, un índice nuevo puede ayudar poco en una columna con baja selectividad y un join voluminoso quizás sea necesario para producir el resultado. Por eso, cuento las consultas, cronometro el loop entero y reviso en el plan cuántas filas y lecturas se produjeron.\n\nEse radar solo elige la primera medición. La próxima capa de la investigación viene de la cuenta.\n\n## Una línea correcta puede desperdiciar el índice\n\nUna de esas líneas suele pasar desapercibida en la revisión. Devuelve los registros de un día entero.\n\n```sql\nWHERE CAST(campo_data_hora AS date) = @data\n```\n\nEl resultado de la pantalla parece correcto. En el plan, la historia puede ser otra. Aplicar `CAST` a la columna obliga a la consulta a transformar los valores antes de la comparación. Con un índice en `campo_data_hora`, esto puede impedir una búsqueda directa por el intervalo, aumentar bastante las lecturas e incluso llevar a un scan.\n\nCuando el pedido es por los registros de un día entero, calculo los bordes fuera de la columna.\n\n```sql\nWHERE campo_data_hora >= @inicio_do_dia\n  AND campo_data_hora < @inicio_do_proximo_dia\n```\n\nSi el inicio es el 16 de julio a la medianoche, el límite siguiente es el 17 de julio a la medianoche. Así entran todos los valores del día 16, incluso aquellos con fracciones de segundo al final, sin depender de `23:59:59.999`.\n\nEl resultado permanece correcto, pero ahora existe un intervalo que el índice puede recorrer. La confirmación viene de la comparación de las lecturas y del operador de acceso en los dos planes. Si la métrica no cambia, la hipótesis no se sostuvo.\n\n## Lee el plan por la secuencia del trabajo",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "336e0d744c9c2ba6",
      "url": "https://imrafaeldev.site/es/articulos/go-intensivo",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos\n\n## Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos\n\nUna guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.\n\n22 de septiembre de 2026\n\n- [Arquitectura](/es/articulos/?topic=arquitetura)\n- [Rendimiento](/es/articulos/?topic=performance)\n- [Mensajería](/es/articulos/?topic=mensageria)\n\nEsta guía es para quien ya trabaja con backend y necesita volver a conversar o implementar servicios Go de producción. Las decisiones que más importan son concurrencia limitada, cancelación, colas finitas, idempotencia y observabilidad.\n\nEl recorte usa Go 1.26. No es una introducción al lenguaje. Repasa la sintaxis rápido y dedica el tiempo a las decisiones que cambian el diseño de un consumer, una API o un pipeline de telemetría.\n\n## Ruta de 30 minutos\n\nTiempo\n\nBloque\n\nPrioridad\n\n0-4 min\n\nTipos, structs, interfaces y errores\n\nRevisión rápida\n\n4-10 min\n\nGoroutines, channels, select y contexto\n\nAlta\n\n10-17 min\n\nConcurrencia limitada y backpressure\n\nMáxima\n\n17-23 min\n\nPipeline IoT resiliente\n\nMáxima\n\n23-26 min\n\nRuntime, memoria y profiling\n\nAlta\n\n26-30 min\n\nArquitectura y entrevista\n\nMáxima\n\n## Fundamentos que aparecen en producción\n\nGo favorece composición, contratos pequeños y flujo explícito. Un valor puede llevar su propia validación sin depender de un framework:\n\nvar ErrOutOfRange = errors.New(\"reading out of range\")\n\ntype Reading struct {\nDeviceID string\nSequence uint64\nValue float64\n}",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "return",
        "context",
        "puede",
        "orden",
        "channel",
        "concurrencia",
        "más",
        "value"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/go-intensivo"
      }
    },
    {
      "id": "33da522e33f08d95",
      "url": "https://imrafaeldev.site/es/experiencias/maxmilhas",
      "title": "Maxmilhas",
      "content": "- [Inicio](/es/)\n- Maxmilhas\n\n## Maxmilhas\n\nMi experiencia en Maxmilhas con automatización posventa e integración de legado.\n\nCargo\n\nIngeniero de Software Full Stack\n\nPeríodo\n\nabr/2023 a oct/2023\n\n## Contexto\n\nEn Maxmilhas, trabajé en un período corto e intenso, en flujos posventa de viajes relacionados con cancelaciones, cambios, cupones y comunicación con clientes.\n\n## Cómo trabajé\n\nDesarrollé microservicios en Node.js, NestJS y Elixir para automatizar procesos que aún dependían de intervención del soporte. Implementé reglas de elegibilidad, expiración y acumulación de cupones, además de cálculos y validaciones para cancelaciones y cambios. En conjunto, esas automatizaciones redujeron en 34% la necesidad de intervención manual.\n\nOtro desafío fue integrar un monolito PHP 5.7, de más de diez años, al CRM comercial sin poner en riesgo el core de la operación. También evolucioné monitoreo de vuelos, notificaciones y mensajes proactivos.\n\n## Lo que me llevé\n\nAprendí a priorizar intervenciones pequeñas y reversibles cuando el resultado debía llegar rápido. En vez de proponer una transformación amplia, me concentré en puntos que liberaban trabajo operativo.",
      "description": "Mi experiencia en Maxmilhas con automatización posventa e integración de legado.",
      "keywords": [
        "maxmilhas",
        "2023",
        "posventa",
        "período",
        "trabajé",
        "cancelaciones",
        "cambios",
        "cupones",
        "para",
        "intervención"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/maxmilhas"
      }
    },
    {
      "id": "35e29738e42fb29a",
      "url": "https://imrafaeldev.site/articles/go-intensive-en",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 2)",
      "content": "A goroutine is not a dedicated OS thread. The runtime schedules it over OS threads. Every goroutine needs a clear owner, stop condition, and wait path.\n\nChannels move work or ownership. Mutexes protect shared state. A buffered channel smooths a temporary speed difference; it does not create unlimited capacity.\n\n```go\nfunc enqueue(ctx context.Context, jobs chan<- Reading, reading Reading) error {\n\tselect {\n\tcase jobs <- reading:\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\treturn context.Cause(ctx)\n\t}\n}\n```\n\nThe producer closes a channel when it knows no more values will be sent. Sending to a closed channel or closing it twice panics. A `nil` channel blocks forever, and disables its `select` case.\n\n`context.Context` carries cancellation, deadlines, and request-scoped metadata. Receive it first, propagate it, call every returned `cancel`, and do not store it in a struct. Cancellation is cooperative: blocking loops must watch `ctx.Done()`.\n\n## Bound concurrency before memory becomes the limit\n\nOne goroutine per message becomes expensive when a downstream slows down. Queues and heap grow, GC gets busier, and the process may fail before CPU looks full.\n\n`errgroup` combines waiting, first-error propagation, and shared cancellation. Set a limit for independent tasks:\n\n```go\nfunc ProcessBatch(ctx context.Context, batch []Reading) error {\n\tg, ctx := errgroup.WithContext(ctx)\n\tg.SetLimit(16)\n\n\tfor _, reading := range batch {\n\t\treading := reading\n\t\tg.Go(func() error {\n\t\t\treturn processOne(ctx, reading)\n\t\t})\n\t}\n\treturn g.Wait()\n}\n```\n\nClassify errors before acting. A database outage can cancel a batch. Invalid, duplicate, or schema-incompatible messages belong in quarantine or a DLQ, not in a failure that stops the whole consumer.\n\nUse `atomic` for an independent flag or counter, `sync.Mutex` for an invariant across fields, and a channel for work transfer. Do not copy a mutex after first use or keep a lock during remote I/O.",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "context",
        "reading",
        "channel",
        "https",
        "errors",
        "device",
        "with",
        "time",
        "return",
        "goroutine"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-go-intensive",
        "slug": "go-intensive",
        "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes",
        "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
        "excerpt": "A 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "articles/go-intensive-en.md"
      }
    },
    {
      "id": "37459f6fd4a587c3",
      "url": "https://imrafaeldev.site/experiences/flapper-en",
      "title": "Flapper | Rafael Pereira",
      "content": "## Context\n\nAt Flapper, I worked on an executive aviation platform whose main product was a PHP monolith more than seven years old, with little useful documentation and no original developers available to explain the system.\n\n## How I worked\n\nThe challenge was not to replace PHP with TypeScript. The application was in production and sustained the business, so I began with domain discovery. I used the database to map relationships, identify bounded contexts, and plan an incremental migration based on the Strangler pattern.\n\nI migrated modules such as people, authentication, and aircraft to Node.js, NestJS, and Go services. To reduce relational dependencies between contexts, we worked with local projections and Kafka events; gRPC, REST, and GraphQL were used according to each integration's needs.\n\n## What I took from it\n\nThe experience consolidated my view of legacy modernization: technology comes after understanding boundaries, risks, and a sequence that preserves the operation. Alongside module delivery, I documented decisions and led workshops so the team could continue the transformation.",
      "description": "My experience at Flapper with incremental modernization of a production legacy system.",
      "keywords": [
        "with",
        "worked",
        "used",
        "contexts",
        "context",
        "flapper",
        "executive",
        "aviation",
        "platform",
        "whose"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-flapper",
        "slug": "flapper",
        "title": "Flapper | Rafael Pereira",
        "description": "My experience at Flapper with incremental modernization of a production legacy system.",
        "company": "Flapper",
        "role": "Full Stack Software Engineer",
        "period": "Sep 2021 to Jun 2022",
        "caseSlug": "undocumented-monolith-modernization",
        "caseSummary": "The case study shows how I investigated the domain from the database and code and led incremental monolith modernization without interrupting the operation.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/flapper-en.md"
      }
    },
    {
      "id": "3802b6d18326b46d",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-en",
      "title": "Derusting logic #01: Group Anagrams (Part 3)",
      "content": "for i := 0; i < len(str); i++ {\n\t\t\tkey[str[i]-'a']++\n\t\t}\n\n\t\tgroups[key] = append(groups[key], str)\n\t}\n\n\tresult := make([][]string, 0, len(groups))\n\n\tfor _, group := range groups {\n\t\tresult = append(result, group)\n\t}\n\n\treturn result\n}\n```\n\n## What changed in complexity?\n\n- Sorting: `O(k log k)` per string and `O(n × k log k)` for `n` strings.\n- Counting: `O(k)` per string and `O(n × k)` for `n` strings.\n\nThe improvement appeared when I realized sorting did work the problem never asked for. The main change is from `O(k log k)` to `O(k)` per string.\n\n## The first solution was not wrong\n\nIt solves the problem and, depending on context, could be enough. The habit I wanted to recover was to keep thinking after the code starts working. Find a solution, return to the problem, and ask: what work is my algorithm doing without needing to?\n\nI am not doing these exercises because LeetCode represents all software engineering work, nor to chase the most sophisticated solution. I am doing them because I noticed that using AI every day reduced how often I insist on a problem alone. I want to reserve space to exercise that again: read, try, fail, review the approach, and only then compare paths.\n\n---\n\n*Series Derusting logic #01 — Group Anagrams. Adapted from the original carousel; problem at [leetcode.com/problems/group-anagrams](https://leetcode.com/problems/group-anagrams/).*",
      "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
      "keywords": [
        "string",
        "group",
        "that",
        "result",
        "same",
        "solution",
        "each",
        "problem",
        "groups",
        "what"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "derusting-logic-group-anagrams",
        "title": "Derusting logic #01: Group Anagrams",
        "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
        "excerpt": "I had not stopped writing code. What changed was outsourcing parts of the reasoning. Group Anagrams was the exercise to recover the habit.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-en.md"
      }
    },
    {
      "id": "38bae66bc095ab8d",
      "url": "https://imrafaeldev.site/en/articles/backend-performance-close-to-data",
      "title": "Most backend performance problems start close to the data (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Most backend performance problems start close to the data\n\n## Most backend performance problems start close to the data\n\nSlow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.\n\nJuly 16, 2026\n\n- [Performance](/en/articles/?topic=performance)\n- [Architecture](/en/articles/?topic=arquitetura)\n\nA slow API and the meeting already fills with solutions before anyone has a measurement. More CPU and RAM show up, cache, queues, microservices, refactoring. Sometimes someone even proposes switching languages. Rarely is the first suggestion to open the execution plan.\n\nThat is why I start the investigation close to the data. Not because the database is the default culprit, but because so much passes through it and that check usually gives a fast signal. If the query is healthy, I rule the database out and follow the request flow.\n\n## Quick fixes can also hide expensive work\n\nCache and queues solve real problems. Bigger machines can also be the right call. When they arrive by reflex, though, those resources may only change the bill size while nobody knows which part of the request is holding the response.\n\nMore CPU reduces resource contention, cache takes some requests out of the path, and a queue absorbs a spike. Meanwhile, a query doing enormous work to return almost nothing stays expensive on every execution. The same goes for a loop firing one call per item.\n\nLatency may drop for a while, the alert stops firing, and the team breathes. When load grows, the expensive operation reappears, now accompanied by larger infrastructure.\n\nThe question that must come before the architecture debate: how much work is this request producing to deliver the result?\n\n## We almost doubled the machine and the dashboard stayed slow",
      "description": "Before adding machines, cache, or queues, measure the request",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "before",
        "with",
        "where",
        "start",
        "column",
        "data"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/en/articles/backend-performance-close-to-data"
      }
    },
    {
      "id": "3af4f75cf3aaaed7",
      "url": "https://imrafaeldev.site/en/projects/md2cv",
      "title": "md2cv (Part 1)",
      "content": "- [Home](/en/)\n- [Projects](/en/projects/)\n- md2cv\n\nOwn product, desktop\n\n## md2cv\n\nProfile and immutable versions stay in the machine's SQLite. The supervised agent only proposes changes under schema; ATS and PDF/DOCX export reuse the same graph, with no SaaS backend owning the data.\n\n[Repository](https://github.com/imrafaeldev/md2cv)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\n- [01Problem](#problem)\n- [02Constraints](#constraints)\n- [03Decision](#decision)\n- [04Current state](#current-state)\n- [05Limitations](#limitations)\n\n## Problem\n\nProfessional profiles scatter across docs, LinkedIn, and exports. Each application asks for a different angle. Adapting a resume with an unbounded LLM invents experience and erases the context of the previous version.\n\nWhat is needed is a versioned graph on the machine that asks what is missing and rejects incomplete output, without promising hiring or automatic ATS approval.\n\n## Constraints\n\nDesktop and local-first (Electron): no proprietary account and no backend owning the data.\n\n- Typed and validated IPC between renderer and main process.\n\n- SQLite with foreign keys, WAL, checksummed migrations, integrity checks, and backup before destructive operations.\n\n- Agents (Codex, Cursor, OpenCode) enter through isolated adapters, not as database owners.\n\n- Job matching never writes to the profile without explicit confirmation.\n\n- External access only on user action (company URL lookup or an AI CLI already configured on the computer); the product does not store provider credentials.\n\n## Decision\n\nReact/Vite renderer separated from the main process. The product organizes work in a single local graph:\n\n- Profile: experiences, education, courses, languages, projects, links, skills, and companies per person.\n\n- Resumes: Markdown, immutable versions, traceable restore, and evolution overview.",
      "description": "Local-first desktop studio for professional profiles, Markdown resumes, immutable versions, ATS, and job matching with supervised agents. Data stays in the machine",
      "keywords": [
        "export",
        "with",
        "product",
        "profile",
        "under",
        "graph",
        "state",
        "resume",
        "projects",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/projects/md2cv"
      }
    },
    {
      "id": "3c01e7b240cffca1",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-en",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 3)",
      "content": "The discomfort came from a wrong expectation: I thought moving to Go would automatically solve the problem. In practice, I had only moved the bottleneck. Before, the limit was clearer on CPU. After, the partitioning strategy started pressuring memory.\n\nThat can happen for several reasons: unnecessary copies, large buffers, slices holding references to bigger arrays, oversized internal queues, or too much work prepared before being processed.\n\nIn the final result, memory still grew about 5%. In that case, the time and CPU gain compensated the loss. But that is not a universal rule. If the production load were much larger, or if the service ran with a thin memory margin, that trade-off could stop being acceptable.\n\nParallelism costs coordination, allocation, synchronization, and observability. There is no free parallel execution.\n\n## When I would keep Node.js\n\nI would keep Node.js without hesitation for I/O orchestration: WebSocket communication, calls to several APIs, database queries, event dispatch, integration between services, and flows where dead time is waiting.\n\nIn those cases, the Event Loop is an excellent choice. It allows high volumes of concurrent operations without creating one thread per request. For event-oriented applications, that is simple, productive, and easy to fit into the JavaScript ecosystem.\n\nThe mistake is pushing that same model onto heavy computation and thinking I/O concurrency becomes CPU parallelism. It does not.\n\n## When I would look at Go\n\nI would start looking at Go when the task is clearly CPU bound: high-volume data computation, batch image processing, heavy aggregations, compression, large buffer transforms, simulations, or any routine where the machine spends more time computing than waiting for external responses.\n\nIn that kind of scenario, goroutines with a worker pool give better control over CPU use. They also make the separation between work units, synchronization, and result collection more explicit.",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "work",
        "loop",
        "problem",
        "event",
        "time",
        "memory",
        "same"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models",
        "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
        "excerpt": "Node.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-en.md"
      }
    },
    {
      "id": "3c2c3123ec6f3c88",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 3)",
      "content": "**Exemplo de aplicação.** Cenário didático: uma busca com timeout que abandona o trabalho lento.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\nfunc fetch(ctx context.Context, id int) (string, error) {\n\ttimer := time.NewTimer(2 * time.Second)\n\tdefer timer.Stop()\n\tselect {\n\tcase <-timer.C:\n\t\treturn fmt.Sprintf(\"item-%d\", id), nil\n\tcase <-ctx.Done():\n\t\treturn \"\", ctx.Err()\n\t}\n}\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)\n\tdefer cancel()\n\n\tresult, err := fetch(ctx, 42)\n\tif err != nil {\n\t\tfmt.Println(\"cancelado:\", err)\n\t\treturn\n\t}\n\tfmt.Println(result)\n}\n```\n\n**Quando escolher outra abordagem.** Use valores de contexto apenas para dados de escopo de requisição (identificador de correlação, credenciais de chamada). Nunca use contexto para parâmetros obrigatórios da função nem para estado mutável compartilhado — passe argumentos explícitos. Se o cancelamento precisa interromper computação que não observa contexto (loop apertado de CPU), verifique `ctx.Done()` manualmente a cada iteração ou reestruture o trabalho em etapas interrompíveis.\n\n## 3. Channels e sincronização: quando o mutex é melhor\n\n**Mecanismo.** Channels transferem *posse de dados* entre goroutines e sincronizam remetente e receptor; `sync.Mutex` (e `sync.RWMutex`) protegem *acesso a estado compartilhado*. A regra prática: use channels para orquestrar (sinalizar conclusão, distribuir tarefas, aplicar backpressure) e mutex para guardar invariantes de uma estrutura acessada por várias goroutines (contadores, caches, mapas).",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "3d54efcf2b1598cf",
      "url": "https://imrafaeldev.site/artigos/design-patterns-adapter",
      "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter (Part 2)",
      "content": "interface CreateDatabaseCustomerProtocol {\ncreateCustomerOnDatabase(\ncustomer: CustomerInputEntity,\n): SuccessfulEntityCreation;\n}\nEm vez de **Controller → Service → Database**, passamos a **Controller → Service → Protocols → Plugin**.\n\nO serviço perde o conhecimento de como o CRUD chega ao banco. Ele é composto pelos protocolos; a implementação concreta entra em tempo de execução — por injeção de dependência. Enquanto usamos PostgreSQL, implementamos os protocolos nos adapters (ou connectors).\n\nCreateDatabaseCustomerProtocol pode ser implementado por CreateDatabaseCustomerPostgresqlAdapter, CreateDatabaseCustomerMysqlAdapter, CreateDatabaseCustomerMongoDBAdapter ou CreateDatabaseCustomerMockedAdapter. O serviço fica assim:\n\nclass CustomerService {\nconstructor(\nprivate readonly createCustomer: CreateDatabaseCustomerProtocol,\n) {}\n\npublic register(customer: CustomerInputEntity): SuccessfulEntityCreation {\nreturn this.createCustomer.createCustomerOnDatabase(customer);\n}\n}\nPara o serviço, tanto faz se o banco devolve JSON, XML ou outro formato — o adapter traduz para o contrato que a regra de negócio espera.\n\n## Vantagens\n\n- **Manutenção:** qualquer plugin pode ser substituído sem reescrever o serviço.\n\n- **Testes:** para testar só a regra de negócio, injete um adapter mock que implementa o mesmo protocolo.\n\n- **Código limpo:** responsabilidades separadas; a regra de negócio não carrega detalhes do driver.\n\n## Próximo passo\n\nPegue um frontend com dezenas de bibliotecas e identifique o que você realmente usa. Escolha uma funcionalidade — converter real em dólar, por exemplo. Descreva o contrato (entrada e saída) e implemente um adapter em cima da biblioteca que hoje faz isso. Repita onde a dependência incomoda.\n\n## Relação com outros padrões",
      "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
      "keywords": [
        "adapter",
        "para",
        "serviço",
        "regra",
        "negócio",
        "contrato",
        "banco",
        "interface",
        "mysql",
        "plugin"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/artigos/design-patterns-adapter"
      }
    },
    {
      "id": "3dabc6114719122a",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-pt-br",
      "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter (Part 1)",
      "content": "Com o padrão Adapter, isolamos a regra de negócio da dependência concreta.\n\n## Cenário inicial\n\nTemos um backend com CRUD simples de usuário: criar, editar, recuperar e deletar por endpoints na API. Os dados vão para um banco qualquer — digamos MySQL — e a estrutura nasce como **Controller → Service → Database**. Até aqui, o time está confortável.\n\nAté alguém decidir: na semana que vem migramos de MySQL para PostgreSQL. A partir daí a casa cai para tecnologia. Além de reestruturar o banco, o time precisa caçar referências ao MySQL: inserções, conexão, queries espalhadas.\n\nNa maioria das vezes isso atrasa entrega, reduz qualidade, pula testes e introduz bugs. Seria melhor diminuir a dependência entre a regra de negócio e quem executa a operação específica — no caso, o banco. O Adapter ajuda a construir o sistema assim.\n\n## Padrão Adapter\n\nTemos um plugin, library, module ou serviço de terceiros que faz algo que queremos na regra de negócio — aqui, persistir dados. O caminho:\n\n1. Definir uma interface com o contrato do que precisamos.\n2. Expor só métodos que fazem sentido no contexto (SOLID).\n3. Implementar a interface em classes que adaptam o código de terceiros.\n\nCriamos `CreateDatabaseCustomerProtocol` com um método `create` que recebe `CustomerInputEntity` e retorna `SuccessfulEntityCreation`:\n\n```typescript\ninterface CustomerInputEntity {\n  name: string;\n  email: string;\n  birthDate: Date;\n}\n\ninterface SuccessfulEntityCreation {\n  readonly id: number;\n  readonly name: string;\n  readonly email: string;\n  readonly birthDate: Date;\n}\n\ninterface CreateDatabaseCustomerProtocol {\n  createCustomerOnDatabase(\n    customer: CustomerInputEntity,\n  ): SuccessfulEntityCreation;\n}\n```\n\nEm vez de **Controller → Service → Database**, passamos a **Controller → Service → Protocols → Plugin**.",
      "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
      "keywords": [
        "adapter",
        "regra",
        "negócio",
        "para",
        "interface",
        "banco",
        "serviço",
        "readonly",
        "dependência",
        "contrato"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter",
        "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
        "excerpt": "Migrar de MySQL para PostgreSQL não precisa reescrever o serviço. O Adapter isola o plugin atrás de um contrato que a regra de negócio entende.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-pt-br.md"
      }
    },
    {
      "id": "3dbb55635418183a",
      "url": "https://imrafaeldev.site/experiencias/infosistemas",
      "title": "Infosistemas",
      "content": "- [Início](/)\n- Infosistemas\n\n## Infosistemas\n\nMinha experiência na Infosistemas com mensageria, integrações e plataformas de mobilidade.\n\nCargo\n\nEngenheiro de Software Sênior / Arquiteto de Software\n\nPeríodo\n\nfev/2025 a mai/2026\n\n## O contexto\n\nNa Infosistemas, atuei em plataformas para locadoras, frotas, montadoras e operações de mobilidade. Era um ambiente enterprise, com integrações, fluxos fiscais e serviços de grande volume, no qual trabalhei próximo de DevOps, SREs e DBAs.\n\n## Como atuei\n\nMinha atuação combinou arquitetura e execução hands-on. Redesignei fluxos entre microsserviços, liderei integrações em NestJS e Go e implementei rastreabilidade de eventos críticos com NestJS e MongoDB. Também evoluí APIs, investiguei problemas de segurança e participei de jornadas digitais e de componentes do webapp quando a continuidade entre backend e interface era necessária.\n\nO ponto que mais orientou meu trabalho foi tornar falhas observáveis e tratáveis desde o desenho. Na mensageria, tratei durabilidade, retry, idempotência e controle de consumo como partes do fluxo, não como correções posteriores.\n\n## O que levo\n\nEssa experiência ampliou minha atuação em sistemas com muitas dependências e especialistas envolvidos. Aprendi a transformar requisitos, riscos e restrições operacionais em decisões que continuassem claras até a validação com o cliente.\n\n## Case relacionado\n\nO case detalha como redesenhei a mensageria RabbitMQ para tornar fluxos críticos mais previsíveis e reduzir falhas intermitentes entre microsserviços.\n\n[Ler case completo](/casos/mensageria-rabbitmq/)",
      "description": "Minha experiência na Infosistemas com mensageria, integrações e plataformas de mobilidade.",
      "keywords": [
        "infosistemas",
        "como",
        "minha",
        "mensageria",
        "integrações",
        "fluxos",
        "entre",
        "case",
        "experiência",
        "plataformas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/infosistemas"
      }
    },
    {
      "id": "3df177f331459b2f",
      "url": "https://imrafaeldev.site/en/articles/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 3)",
      "content": "if (existsName) {\nthrow new UserAlreadyExistsException(\n`the name ${name} already exists`,\n);\n}\n\nreturn this.createUserProtocol.register(name);\n}\n}\nThe usecase defines *what* (validate name, register). It does not define *how* to fetch or persist. The rule stays independent of lib, framework, and database.\n\nWatch out: a usecase that only delegates to the protocol without validating may be pushing business rules into the adapter. In CreateUserUsecase, the duplicate-name check is Core’s obligation.\n\n### Exceptions\n\nUserAlreadyExistsException belongs to Core: an invalid rule flow is also a rule. Each failure mapped to a known exception helps maintenance. Base with code (later becomes HTTP status at the edge):\n\n// core/exceptions/IBaseException.ts\nexport abstract class IBaseException extends Error {\ncode: number;\n\nconstructor(message: string) {\nsuper(message);\n}\n}\n// core/exceptions/UserAlreadyExistsException.ts\nexport class UserAlreadyExistsException extends IBaseException {\nconstructor(message?: string) {\nsuper(message ?? \"User already exists\");\nthis.code = 400;\n}\n}\nThe usecase **throws** exceptions; it does **not** handle them. Mapping unknown type → known type belongs in adapter or infra.\n\n### Protocols\n\nCreateUserProtocol and GetUserByNameProtocol are contracts for external-device access. A protocol exists to inform or trigger external action — **not** to process business rules. Preference: one public method per protocol.\n\n// core/protocols/CreateUserProtocol.ts\nexport abstract class CreateUserProtocol {\nabstract register(name: string): Promise&#x3C;UserEntity>;\n}\n// core/protocols/GetUserByNameProtocol.ts\nexport abstract class GetUserByNameProtocol {\nabstract getByName(name: string): Promise&#x3C;UserEntity | null>;\n}\nCore is the center; the adaptation layer connects the rest.\n\n## Adapter",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "architecture",
        "return",
        "export",
        "class",
        "adapters"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/en/articles/typescript-clean-architecture"
      }
    },
    {
      "id": "400ae41d59c1ea46",
      "url": "https://imrafaeldev.site/artigos/design-patterns-adapter",
      "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter (Part 3)",
      "content": "No artigo [Design Patterns: Strategy](/artigos/design-patterns-strategy/), o foco é trocar algoritmos atrás de um contrato. O Adapter isola dependências externas atrás de uma interface própria. Os dois se complementam: Strategy varia comportamento; Adapter traduz o mundo de fora.\n\n---\n\n*Publicado originalmente no [LinkedIn](https://www.linkedin.com/pulse/pare-de-ser-ref%C3%A9m-das-depend%C3%AAncias-diga-bem-vindo-ao-design-rafael/) em 26 de abril de 2022.*\n\nAdapter: protocolo e implementações\nCustomerService usa CreateDatabaseCustomerProtocol. MysqlAdapter e PostgresqlAdapter implementam o contrato e traduzem para cada banco.\n\nCamadas: antes e depois\nAcoplamento direto ao MySQL dificulta migração. Com protocolo e adapter, troca-se o plugin sem reescrever o serviço.",
      "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
      "keywords": [
        "adapter",
        "para",
        "serviço",
        "regra",
        "negócio",
        "contrato",
        "banco",
        "interface",
        "mysql",
        "plugin"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/artigos/design-patterns-adapter"
      }
    },
    {
      "id": "4012281e736c859a",
      "url": "https://imrafaeldev.site/es/proyectos",
      "title": "Proyectos (Part 1)",
      "content": "- [Inicio](/es/)\n- Proyectos\n\n## Proyectos\n\nArtefactos con problema, restricciones y estado actual del repositorio.\n\nRepositorio público\n\n## [SMS Manager](/es/proyectos/sms-manager/)\n\nImportar CSV no puede bloquear la API Nest. La campaña persiste y publica en la cola RabbitMQ; consumidores en TypeScript, Go o Rust graban el resultado en Mongo. La API no espera a la operadora.\n\n[Abrir el proyecto](/es/proyectos/sms-manager/)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\nExperimento documentado\n\n## [goc_mcp](/es/proyectos/goc-mcp/)\n\nMaestro (Codex/Cursor) delega vía MCP; daemon FIFO y workers OpenCode ejecutan con estado en SQLite. La orquestación funcionó, pero la medición no confirmó reducción de costo o tiempo.\n\n[Abrir el proyecto](/es/proyectos/goc-mcp/)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\nProducto propio, desktop\n\n## [md2cv](/es/proyectos/md2cv/)\n\nPerfil y versiones inmutables quedan en el SQLite de la máquina. El agente supervisado solo propone cambios bajo schema; ATS y exportación en PDF/DOCX reutilizan el mismo grafo, sin backend SaaS dueño de los datos.\n\n[Abrir el proyecto](/es/proyectos/md2cv/)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\nCLI pública; revisión por IA aún mock\n\n## [DiffVision](/es/proyectos/diffvision/)\n\nEl diff Git abre en la UI local; comentarios y exportación en Markdown quedan en el repositorio. La revisión visual por IA permanece mock.\n\n[Abrir el proyecto](/es/proyectos/diffvision/)\n\nRevisão local-first\nDiff no disco, UI local, comentários e export em `.diffvision/`. A plataforma remota fica de fora; a revisão por IA visual ainda é mock.\n\nEstación de trabajo editorial",
      "description": "Artefactos con problema, restricciones y estado actual del repositorio.",
      "keywords": [
        "proyectos",
        "abrir",
        "proyecto",
        "sqlite",
        "local",
        "diffvision",
        "para",
        "estado",
        "repositorio",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/es/proyectos"
      }
    },
    {
      "id": "40d3f8523cc618d0",
      "url": "https://imrafaeldev.site/es/proyectos/goc-mcp",
      "title": "goc_mcp",
      "content": "- [Inicio](/es/)\n- [Proyectos](/es/proyectos/)\n- goc_mcp\n\nExperimento documentado\n\n## goc_mcp\n\nMaestro (Codex/Cursor) delega vía MCP; daemon FIFO y workers OpenCode ejecutan con estado en SQLite. La orquestación funcionó, pero la medición no confirmó reducción de costo o tiempo.\n\n[Repositorio](https://github.com/imrafaeldev/goc_mcp)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\n- [01Problema](#problema)\n- [02Restricciones](#restricciones)\n- [03Decisión](#decision)\n- [04Estado actual](#estado-actual)\n- [05Limitaciones](#limitaciones)\n\n## Problema\n\nUn maestro (Codex o Cursor) necesita delegar trabajo a workers OpenCode con ciclo de vida explícito: planear, iniciar, acompañar, responder, cancelar y recuperar, sin recursión infinita de agentes.\n\n## Restricciones\n\nTodo es local. El daemon autentica en loopback. Hay límite global y por workspace. Las tareas interrumpidas necesitan reconciliarse. La corrupción de estado no puede volverse escritura ciega. En el camino feliz: un worker OpenCode por vez, sin descomposición automática que deje al maestro sin control.\n\n## Decisión\n\nImplementación en Go: gateways MCP por stdio, daemon único, máquina de estados y ejecutor FIFO. Persistencia en SQLite con WAL; artefactos en JSONL. Adaptador OpenCode con servidor como camino principal y CLI como fallback. Aislamiento contra delegación recursiva y diagnóstico solo lectura si el estado se corrompe.\n\n## Estado actual\n\nRepositorio con pruebas, ADRs y benchmarks. La orquestación funcionó. Las mediciones publicadas en el propio proyecto no confirmaron la hipótesis de reducir tiempo y costo.\n\n## Limitaciones\n\nEs un experimento. No afirma ganancia de productividad de ingeniería de agentes en producción. El resultado negativo de la hipótesis forma parte del artefacto.",
      "description": "Orquestación local de agentes vía MCP en Go: maestro, daemon FIFO, workers OpenCode y SQLite. La entrega funcionó, pero la hipótesis de costo y tiempo no se confirmó en esta medición.",
      "keywords": [
        "opencode",
        "estado",
        "maestro",
        "daemon",
        "fifo",
        "sqlite",
        "proyectos",
        "experimento",
        "codex",
        "cursor"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/proyectos/goc-mcp"
      }
    },
    {
      "id": "42965d920eacc0fe",
      "url": "https://imrafaeldev.site/en/resume",
      "title": "Resume (Part 3)",
      "content": "Built Node.js and NestJS microservices and automated post-sale rules, calculations, and validations; the change reduced the need for manual support intervention by 34%.\n\n[Read the full experience](/en/experience/maxmilhas/)\n\n- Jun 2022 to Apr 2023 Remote South System (assigned to QUIQ/Itaú) Backend Engineer Expand experience Collapse experience\nArchitected a multi-tenant marketplace with Node.js and asynchronous services in Go, structuring critical tests with approximately 95% coverage.\n\nContext\n\nThe work focused on a white-label, multi-tenant marketplace for financial institutions, prepared to incorporate new banks without client-specific forks.\n\nContribution\n\nParticipated in architecture and rule refinement, using Node.js and asynchronous Go services to isolate tenant variations; structured critical tests with approximately 95% coverage.\n\n[Read the full experience](/en/experience/south-system-quiq-itau/)\n\n- Sep 2021 to Jun 2022 Remote Flapper Full Stack Software Engineer Expand experience Collapse experience\nMigrated people, authentication, and aircraft modules to Node.js, NestJS, and Go services; domain separation reduced the table count by approximately 25%.\n\nContext\n\nThe executive aviation platform depended on a monolith over seven years old, with little documentation and rules that were difficult to separate without interrupting operations.\n\nContribution\n\nMapped domain boundaries and led an incremental modernization, migrating modules to Node.js, NestJS, and Go services; the separation reduced the table count by approximately 25%.\n\n[Read the full experience](/en/experience/flapper/)\n\n- Jan 2021 to Aug 2021 Remote Sustentec Mid-level Full Stack Software Engineer Expand experience Collapse experience\nMaintained and evolved a laboratory management system with Java, Spring Boot, and Angular, adding integration tests where none existed before.\n\nContext",
      "description": "Rafael Pereira",
      "keywords": [
        "experience",
        "with",
        "nestjs",
        "full",
        "engineer",
        "node",
        "backend",
        "remote",
        "expand",
        "collapse"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/en/resume"
      }
    },
    {
      "id": "434d33c1726c8b91",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-pt-br",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 2)",
      "content": "Esse é o tipo de cenário em que `Promise.all` pode enganar. Ele passa a sensação de paralelismo, mas não transforma automaticamente trabalho pesado de CPU em execução paralela real. Se as tarefas são computação intensa e rodam no mesmo thread principal, o Event Loop fica ocupado.\n\nO resultado pode ser pior do que o esperado: bloqueio do loop, aumento de latência, pior responsividade e maior pressão sobre CPU e memória.\n\nO problema não era Node.js ser ruim. O problema era usar o modelo padrão do Node para uma carga que exigia outro tipo de execução.\n\n## Onde Go entrou melhor\n\nA solução foi reescrever esse processo em Go usando goroutines. A ideia era dividir o cálculo em chunks menores, processar esses pedaços em paralelo e sincronizar apenas no final.\n\nEsse desenho encaixava melhor no problema porque o trabalho era CPU bound e podia ser dividido. Em vez de um fluxo centralizado tentando coordenar várias operações pesadas, o processamento passou a ser distribuído em unidades menores de execução.\n\nCom um worker pool, por exemplo, dá para controlar o número de goroutines, limitar o fan-out, usar melhor os cores disponíveis e evitar que o sistema dispare trabalho sem limite.\n\nO ganho apareceu. O tempo total caiu cerca de 25%. O processo que ficava na casa dos 30 segundos passou a rodar em algo próximo de 22,5 segundos. Também houve melhor uso de CPU.\n\nMas a parte importante da história não é “Go resolveu”. A parte importante é que Go resolveu um lado do problema e revelou outro.\n\n## O gargalo pode mudar de lugar\n\nA primeira dificuldade foi garantir que o resultado em Go fosse igual ao resultado em Node.js. Isso é menos glamouroso do que falar sobre concorrência, mas é o que separa otimização real de regressão mascarada.\n\nSe o cálculo fica mais rápido e muda o resultado financeiro, a melhoria não vale nada.",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "mais",
        "mesmo",
        "tempo",
        "trabalho",
        "loop",
        "problema",
        "pode"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência",
        "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
        "excerpt": "Node.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-pt-br.md"
      }
    },
    {
      "id": "441d6fef660a067d",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 1)",
      "content": "Este guia é o aprofundamento do roteiro de 30 minutos de Go. Ele parte dos mesmos fundamentos — goroutines, channels, contexto, backpressure e idempotência — para discutir com mais calma quando cada decisão de desenho se aplica.\n\nSe você ainda não passou pela revisão rápida, comece pelo [roteiro de 30 minutos](/artigos/intensivao-go/) e volte aqui para aprofundar cada bloco.\n\n## Como usar este guia\n\nAvance na ordem proposta: cada seção isola uma decisão de desenho, descreve o mecanismo, a falha que ele trata, um exemplo autocontido e o critério para escolher outra abordagem. Todos os cenários abaixo são didáticos e hipotéticos — servem para treinar leitura de trade-offs, não descrevem sistemas reais. Leia com um editor aberto e adapte os snippets ao seu próprio exercício antes de levar qualquer padrão para um sistema real.\n\n## 1. Modelo de execução: goroutines, scheduler e GOMAXPROCS\n\n**Mecanismo.** Goroutines são unidades leves de execução multiplexadas pelo scheduler do runtime sobre threads do sistema operacional. `GOMAXPROCS` define quantas threads podem executar código Go simultaneamente; por padrão, acompanha o número de CPUs disponíveis. Desde o Go 1.14, as goroutines são assincronamente preemptíveis: o scheduler pode interrompê-las mesmo em loops apertados sem pontos explícitos de cooperação, distribuindo trabalho sem que cada tarefa exija uma thread dedicada.\n\n**Falha ou limite que ele trata.** O modelo evita o custo de uma thread por tarefa concorrente e reduz troca de contexto do sistema operacional. O limite aparece quando se confunde concorrência com paralelismo: criar milhares de goroutines bloqueadas em I/O lento é barato, mas criar milhares de goroutines em loop apertado de CPU com `GOMAXPROCS` baixo apenas serializa o trabalho e aumenta pressão sobre o escalonador e o coletor de lixo.\n\n**Exemplo de aplicação.** Cenário didático: processar uma lista de itens independentes em paralelo, limitada ao número de CPUs.\n\n```go\npackage main",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "4439b944cba89230",
      "url": "https://imrafaeldev.site/projects/sms-manager-pt-br",
      "title": "SMS Manager",
      "content": "## Problema\n\nCampanhas de SMS partem de arquivos CSV, usuários, empresas e autenticação entre serviços. Validar e persistir no mesmo processo que dispara milhares de mensagens acopla a API ao ritmo da operadora e da fila.\n\n## Restrições\n\nO ambiente precisa ser reproduzível. Autenticação entre serviços não pode depender de JWT opaco sem revogação. Consumidores em mais de uma linguagem existem para comparar o mesmo contrato de mensageria.\n\n## Decisão\n\nSete aplicações: APIs NestJS de usuários/autenticação e de empresas/campanhas; consumidores equivalentes em Node.js/TypeScript, Go e Rust; provisionador declarativo de exchanges, filas e bindings; gerador de massa CSV. PostgreSQL/TypeORM para dados relacionais na API; MongoDB para resultado do consumo; Redis para cache de token opaco (revogável); gRPC para autenticação entre serviços; RabbitMQ com exchanges topic para os lotes. A API publica e segue sem bloquear no ritmo da operadora.\n\n## Estado atual\n\nRepositório público com Docker Compose para PostgreSQL, MongoDB, Redis e RabbitMQ. A arquitetura separa domínio, aplicação e infraestrutura nos contextos de usuários, autenticação e empresas; os três consumidores implementam o mesmo contrato de mensagem.\n\n## Limitações\n\nÉ um artefato de estudo e operação local de mensageria, não um produto comercial com SLA de operadora. Os três consumidores demonstram o contrato; não afirmam que as três linguagens rodam juntas em produção do autor.",
      "description": "Campanhas de SMS desacopladas: a API Nest persiste e publica, a fila entrega e consumidores em TypeScript, Go ou Rust gravam o resultado. Token opaco, Redis e gRPC entre serviços.",
      "keywords": [
        "para",
        "autenticação",
        "consumidores",
        "usuários",
        "empresas",
        "entre",
        "serviços",
        "mesmo",
        "operadora",
        "não"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "projeto-sms-manager",
        "slug": "sms-manager",
        "title": "SMS Manager",
        "description": "Campanhas de SMS desacopladas: a API Nest persiste e publica, a fila entrega e consumidores em TypeScript, Go ou Rust gravam o resultado. Token opaco, Redis e gRPC entre serviços.",
        "excerpt": "Importar CSV não pode travar a API Nest. A campanha persiste e publica na fila RabbitMQ; consumidores em TypeScript, Go ou Rust gravam o resultado no Mongo. A API não espera a operadora.",
        "repoUrl": "https://github.com/imrafaeldev/sms-manager",
        "status": "Repositório público",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/sms-manager-pt-br.md"
      }
    },
    {
      "id": "4454038a5ff73b19",
      "url": "https://imrafaeldev.site/en",
      "title": "Rafael Pereira, senior software engineer (Part 3)",
      "content": "Orquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\nOwn product, desktop\n\n### [md2cv](/en/projects/md2cv/)\n\nProfile and immutable versions stay in the machine's SQLite. The supervised agent only proposes changes under schema; ATS and PDF/DOCX export reuse the same graph, with no SaaS backend owning the data.\n\n[Open the project](/en/projects/md2cv/)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\n[See all projects](/en/projects/)\n\nPath\n\n## A trajectory of systems, not of job titles\n\n-\n2025 to 2026\n\n### Architecture\n\nRental and fleet platforms. The public proof from this period is the messaging redesign.\n\nInfosistemas\n\n-\n2023 to 2025\n\n### Analytics on an external database\n\nSQL Server owned by another team. The dashboard had to work without relying on a schema we did not control.\n\nVBET\n\n-\n2023\n\n### Post-sale over a legacy core\n\nPHP 5.7 still ran the operation. Automation had to free support without rewriting the monolith.\n\nMaxmilhas\n\n-\n2022 to 2023\n\n### Multi-tenant marketplace\n\nWhite-label so banks could join without a per-client fork.\n\nSouth System / QUIQ-Itaú\n\n-\n2021 to 2022\n\n### Incremental modernization\n\nThe monolith had no original authors. Migration started with the parts of the database we could still read.\n\nFlapper\n\nPractice\n\n## How the work proceeds\n\nEach step constrains the next. Without those constraints, architecture becomes personal taste.\n\nHow the work proceeds\n\n- Context\n- Constraint\n- Decision\n- Evidence\n- Limit\n\n01\n\n### Context\n\nWho is hurt when this breaks, and in which operation.\n\n02\n\n### Constraint\n\nWhat cannot stop, change, or be treated as extra capacity.\n\n03\n\n### Decision\n\nThe path taken, with the discarded alternative kept visible.\n\n04\n\n### Evidence\n\nWhat was measured, in which scenario, with whose authorship.\n\n05",
      "description": "Institutional portfolio and editorial hub for Rafael Pereira. I work at the points where simple systems stop being simple.",
      "keywords": [
        "cases",
        "projects",
        "case",
        "open",
        "with",
        "systems",
        "stop",
        "decision",
        "contact",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/en"
      }
    },
    {
      "id": "4477b3c7e08e57d0",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-pt-br",
      "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter (Part 3)",
      "content": "*Publicado originalmente no [LinkedIn](https://www.linkedin.com/pulse/pare-de-ser-ref%C3%A9m-das-depend%C3%AAncias-diga-bem-vindo-ao-design-rafael/) em 26 de abril de 2022.*",
      "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
      "keywords": [
        "adapter",
        "regra",
        "negócio",
        "para",
        "interface",
        "banco",
        "serviço",
        "readonly",
        "dependência",
        "contrato"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter",
        "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
        "excerpt": "Migrar de MySQL para PostgreSQL não precisa reescrever o serviço. O Adapter isola o plugin atrás de um contrato que a regra de negócio entende.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-pt-br.md"
      }
    },
    {
      "id": "44b93125ab034b49",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 4)",
      "content": "Classes implementing protocols. Each adapts **one** external device (ORM, HTTP client, queue, filesystem).\n\nNaming convention:\n\n- **Repositories** — protocol tied to a database (familiar vocabulary).\n- **Connectors** — return data without being a \"table\" (e.g. `ClientHttpFetchConnector`, `ClientHttpAxiosConnector`).\n- **Handlers** — process without synchronous return (e.g. publishing to Kafka).\n\nOther names are valid; the criterion is one adapter per device.\n\nIn the CRUD, only repository (mock):\n\n```typescript\n// adapters/repositories/UsersMockRepository.ts\nexport class UsersMockRepository\n  implements\n    GetUserByIdProtocol,\n    GetUserByNameProtocol,\n    CreateUserProtocol,\n    UpdateUserProtocol,\n    DeleteUserProtocol\n{\n  private db: DbConnector;\n\n  constructor() {\n    this.db = mockDbConnector;\n  }\n\n  async getById(id: string): Promise<UserEntity> {\n    return this.db.users.getById(id);\n  }\n\n  async getByName(name: string): Promise<UserEntity | null> {\n    return this.db.users.getByName(name);\n  }\n\n  async register(name: string): Promise<UserEntity> {\n    return this.db.users.register(name);\n  }\n\n  async update(id: string, name: string): Promise<UserEntity> {\n    return this.db.users.update(id, name);\n  }\n\n  async delete(id: string): Promise<void> {\n    return this.db.users.delete(id);\n  }\n}\n```\n\nMock connector:",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "459c44503b8d854b",
      "url": "https://imrafaeldev.site/artigos/intensivao-golang-avancado",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 3)",
      "content": "**Mecanismo.** context.Context propaga cancelamento, deadline e valores de escopo de requisição ao longo de uma cadeia de chamadas. O dono do contexto (normalmente a borda de entrada: handler HTTP, consumidor de fila, função main) cria um contexto cancelável ou com timeout; as funções internas apenas observam &#x3C;-ctx.Done() e retornam ctx.Err(). Contexto é imutável: WithCancel, WithTimeout e WithValue derivam um filho sem alterar o pai.\n\n**Falha ou limite que ele trata.** Sem ownership claro, goroutines órfãs continuam trabalhando depois que o cliente desistiu, o deploy desligou ou o timeout estourou — desperdiçando CPU, conexões e memória. O limite do mecanismo: contexto não cancela código por força; se a função ignorar ctx.Done() ou bloquear em operação sem suporte a contexto, o cancelamento nunca acontece.\n\n**Exemplo de aplicação.** Cenário didático: uma busca com timeout que abandona o trabalho lento.\n\npackage main\n\nimport (\n\"context\"\n\"fmt\"\n\"time\"\n)\n\nfunc fetch(ctx context.Context, id int) (string, error) {\ntimer := time.NewTimer(2 * time.Second)\ndefer timer.Stop()\nselect {\ncase &#x3C;-timer.C:\nreturn fmt.Sprintf(\"item-%d\", id), nil\ncase &#x3C;-ctx.Done():\nreturn \"\", ctx.Err()\n}\n}\n\nfunc main() {\nctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)\ndefer cancel()\n\nresult, err := fetch(ctx, 42)\nif err != nil {\nfmt.Println(\"cancelado:\", err)\nreturn\n}\nfmt.Println(result)\n}\n**Quando escolher outra abordagem.** Use valores de contexto apenas para dados de escopo de requisição (identificador de correlação, credenciais de chamada). Nunca use contexto para parâmetros obrigatórios da função nem para estado mutável compartilhado — passe argumentos explícitos. Se o cancelamento precisa interromper computação que não observa contexto (loop apertado de CPU), verifique ctx.Done() manualmente a cada iteração ou reestruture o trabalho em etapas interrompíveis.\n\n## 3. Channels e sincronização: quando o mutex é melhor",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "goroutines",
        "sync",
        "func",
        "contexto",
        "quando",
        "done",
        "mutex",
        "limite",
        "context"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-golang-avancado"
      }
    },
    {
      "id": "472c08a45240d9e2",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-en",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 5)",
      "content": "*Originally published on [LinkedIn](https://www.linkedin.com/pulse/goroutines-vs-event-loop-compara%C3%A7%C3%A3o-errada-entre-dois-s-pereira-qtgle/) on June 24, 2026.*",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "work",
        "loop",
        "problem",
        "event",
        "time",
        "memory",
        "same"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models",
        "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
        "excerpt": "Node.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-en.md"
      }
    },
    {
      "id": "47881882bc7a4905",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-en",
      "title": "Derusting logic #01: Group Anagrams (Part 2)",
      "content": "1. `sortString(str)` turns the string into bytes, sorts the characters, and returns a new string.\n2. `sortedStr := sortString(str)` produces the key for that term.\n3. `mapping[sortedStr] = append(...)` uses that key to accumulate anagrams in the same group.\n4. For `eat`, `tea`, and `ate`, `sortedStr` is always `aet`.\n\nThe map starts looking like this:\n\n```text\n\"aet\" -> [\"eat\", \"tea\", \"ate\"]\n\"ant\" -> [\"tan\", \"nat\"]\n\"abt\" -> [\"bat\"]\n```\n\nI compute one key and add the word directly to the matching group. The `map` avoids comparing every string with every other.\n\n## Where is the cost of this approach?\n\nTo discover the key, I sort each string. If a string has `k` characters, that sort costs roughly `O(k log k)`. Repeating for `n` strings, the dominant part is `O(n × k log k)`.\n\n### Why drop the sort?\n\nFor `eat`, I sorted characters to reach `aet`. For `tea`, I sorted again to reach the same `aet`. Sorting works because it creates a shared representation. But the problem does not require sorting anything.\n\nTo know whether two strings are anagrams, it is enough to check whether they hold the same count of each letter.\n\n## That observation changes the solution\n\nInstead of placing letters in the same order, I can count how many times each appears. In this exercise, inputs use lowercase letters from `a` to `z`. Each string can be represented by 26 counters.\n\n`tea` and `ate` produce the same count. I do not need to rearrange any character. I just walk the string and count occurrences. For `eat`, the relevant part is:\n\n```text\na = 1\ne = 1\nt = 1\n```\n\n### Counting solution\n\n`var key [26]uint8` creates 26 slots, one per letter from `a` to `z`. `key[str[i]-'a']++` finds each character's slot and increments the counter. Then `groups[key] = append(groups[key], str)` uses the frequency vector itself as the group key.\n\n```go\nfunc groupAnagrams(strs []string) [][]string {\n\tgroups := make(map[[26]uint8][]string, len(strs))\n\n\tfor _, str := range strs {\n\t\tvar key [26]uint8",
      "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
      "keywords": [
        "string",
        "group",
        "that",
        "result",
        "same",
        "solution",
        "each",
        "problem",
        "groups",
        "what"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "derusting-logic-group-anagrams",
        "title": "Derusting logic #01: Group Anagrams",
        "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
        "excerpt": "I had not stopped writing code. What changed was outsourcing parts of the reasoning. Group Anagrams was the exercise to recover the habit.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-en.md"
      }
    },
    {
      "id": "47be6ab03c50e393",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-es",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 3)",
      "content": "```go\nfunc groupAnagrams(strs []string) [][]string {\n\tgroups := make(map[[26]uint8][]string, len(strs))\n\n\tfor _, str := range strs {\n\t\tvar key [26]uint8\n\n\t\tfor i := 0; i < len(str); i++ {\n\t\t\tkey[str[i]-'a']++\n\t\t}\n\n\t\tgroups[key] = append(groups[key], str)\n\t}\n\n\tresult := make([][]string, 0, len(groups))\n\n\tfor _, group := range groups {\n\t\tresult = append(result, group)\n\t}\n\n\treturn result\n}\n```\n\n## ¿Qué cambió en complejidad?\n\n- Ordenación: `O(k log k)` por string y `O(n × k log k)` para `n` strings.\n- Conteo: `O(k)` por string y `O(n × k)` para `n` strings.\n\nLa mejora apareció cuando percibí que la ordenación hacía un trabajo que el problema no exigía. El cambio principal es de `O(k log k)` a `O(k)` por string.\n\n## La primera solución no estaba equivocada\n\nResuelve el problema y, según el contexto, podría bastar. El hábito que quería recuperar era seguir pensando después de que el código empieza a funcionar. Encontrar una solución, volver al problema y preguntar: ¿qué trabajo está haciendo mi algoritmo sin necesitarlo?\n\nNo hago estos ejercicios porque LeetCode represente todo el trabajo de ingeniería de software, ni para disputar la solución más sofisticada. Los hago porque percibí que usar IA todos los días redujo la cantidad de veces en que necesito insistir solo en un problema. Quiero reservar espacio para ejercitarlo de nuevo: leer, intentar, errar, revisar el enfoque y solo después comparar caminos.\n\n---\n\n*Serie Desenoxidando la lógica #01 — Group Anagrams. Adaptado del carrusel autoral; problema en [leetcode.com/problems/group-anagrams](https://leetcode.com/problems/group-anagrams/).*",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "clave",
        "solución",
        "result",
        "problema",
        "groups",
        "group",
        "mismo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "desenoxidando-logica-group-anagrams",
        "title": "Desenoxidando la lógica #01: Group Anagrams",
        "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
        "excerpt": "Yo no había dejado de escribir código. Lo que cambió fue tercerizar partes del razonamiento. Group Anagrams fue el ejercicio para recuperar el hábito.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-es.md"
      }
    },
    {
      "id": "47ed64a4703c5715",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 8)",
      "content": "// core/usecases/GetUserUsecase.ts\nexport class GetUserUsecase implements GetUser {\n  constructor(private readonly getProtocol: GetUserByIdProtocol) {}\n\n  async execute(id: string): Promise<UserEntity> {\n    return this.getProtocol.getById(id);\n  }\n}\n```\n\n### Actualización\n\n```typescript\n// core/features/UpdateUser.ts\nexport abstract class UpdateUser {\n  abstract execute(id: string, name: string): Promise<UserEntity>;\n}\n\n// core/usecases/UpdateUserUsecase.ts\nexport class UpdateUserUsecase implements UpdateUser {\n  constructor(\n    private readonly updateProtocol: UpdateUserProtocol,\n    private readonly getByIdProtocol: GetUserByIdProtocol,\n  ) {}\n\n  async execute(id: string, name: string): Promise<UserEntity> {\n    const exists = await this.getByIdProtocol.getById(id);\n\n    if (!exists) {\n      throw new UserNotExistsException(`User with id ${id} not exists`);\n    }\n\n    return this.updateProtocol.update(id, name);\n  }\n}\n```\n\n### Eliminación\n\n```typescript\n// core/features/DeleteUser.ts\nexport abstract class DeleteUser {\n  abstract execute(id: string): Promise<void>;\n}\n\n// core/usecases/DeleteUserUsecase.ts\nexport class DeleteUserUsecase implements DeleteUser {\n  constructor(\n    private readonly deleteProtocol: DeleteUserProtocol,\n    private readonly getByIdProtocol: GetUserByIdProtocol,\n  ) {}\n\n  async execute(id: string): Promise<void> {\n    const exists = await this.getByIdProtocol.getById(id);\n\n    if (!exists) {\n      throw new UserNotExistsException(`User with id ${id} not exists`);\n    }\n\n    return this.deleteProtocol.delete(id);\n  }\n}\n```\n\n### Exception y protocols restantes\n\n```typescript\nexport class UserNotExistsException extends IBaseException {\n  constructor(message: string) {\n    super(message);\n    this.code = 404;\n  }\n}\n```\n\n```typescript\n// core/protocols/GetUserByIdProtocol.ts\nexport abstract class GetUserByIdProtocol {\n  abstract getById(id: string): Promise<UserEntity>;\n}",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 7,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "488e0439621cd299",
      "url": "https://imrafaeldev.site/experiences/braistech-pt-br",
      "title": "Braistech | Rafael Pereira",
      "content": "## O contexto\n\nNa Braistech, vivi uma das minhas primeiras experiências de produto em um ambiente pequeno, com pouca gente e responsabilidade distribuída. O domínio envolvia contratos de criptoativos e movimentações financeiras.\n\n## Como atuei\n\nLiderei a estruturação do sistema principal com Node.js e NestJS, participei do desenho de microsserviços para o núcleo do negócio e desenvolvi aplicações em Flutter. Também construí um sistema de contratos e trabalhei com integrações de pagamento relacionadas ao ecossistema da Binance.\n\nParticipei ainda da transição de uma organização baseada em MVC para uma arquitetura mais próxima de Clean Architecture. O objetivo era reduzir acoplamento e facilitar a manutenção de um sistema em crescimento, enquanto eu orientava desenvolvedores juniores nas decisões do código.\n\n## O que levo\n\nEssa etapa consolidou meu interesse por backend e arquitetura. O contato com produto completo também me deu uma visão full stack que segue útil nas conversas com frontend e produto.",
      "description": "Minha experiência na Braistech com produto, microsserviços e criptoativos.",
      "keywords": [
        "produto",
        "sistema",
        "contratos",
        "participei",
        "para",
        "também",
        "arquitetura",
        "contexto",
        "braistech",
        "vivi"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-braistech",
        "slug": "braistech",
        "title": "Braistech | Rafael Pereira",
        "description": "Minha experiência na Braistech com produto, microsserviços e criptoativos.",
        "company": "Braistech",
        "role": "Engenheiro de Software Full Stack Pleno",
        "period": "nov/2019 a jan/2021",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/braistech-pt-br.md"
      }
    },
    {
      "id": "48b808136bed57cd",
      "url": "https://imrafaeldev.site/experiences/azify-es",
      "title": "Azify | Rafael Pereira",
      "content": "## Contexto\n\nEn Azify, trabajé como consultor en infraestructura financiera para fintechs y bancos pequeños. El producto reunía capacidades como Pix, transferencias, tarjetas, billetera digital y otros servicios bancarios.\n\n## Cómo trabajé\n\nParticipé en decisiones arquitectónicas y ayudé a establecer prácticas de desarrollo para un entorno donde consistencia, seguridad y estabilidad tenían impacto financiero directo. Desarrollé un motor de liquidación en NestJS con integraciones multi-exchange, monitoreo de riesgo y controles de compliance.\n\nTambién trabajé en integraciones de exchanges y blockchains para flujos transaccionales y en una plataforma BaaS multi-tenant con OAuth 2.0, JWT y cifrado. Mediante profiling de consultas, revisión de índices y optimización de Redis, reduje en 30% la latencia de APIs financieras críticas.\n\n## Lo que me llevé\n\nEsta consultoría retomó partes recurrentes de mi trayectoria, como pagos, multi-tenancy y sistemas transaccionales, en un contexto con mayor responsabilidad sobre autorización y consistencia.",
      "description": "Mi consultoría en Azify con liquidación, BaaS y servicios financieros.",
      "keywords": [
        "trabajé",
        "como",
        "para",
        "contexto",
        "consistencia",
        "integraciones",
        "transaccionales",
        "azify",
        "consultor",
        "infraestructura"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-azify",
        "slug": "azify",
        "title": "Azify | Rafael Pereira",
        "description": "Mi consultoría en Azify con liquidación, BaaS y servicios financieros.",
        "company": "Azify",
        "role": "Ingeniero Backend Senior — Consultoría",
        "period": "mar/2025 a jun/2025",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/azify-es.md"
      }
    },
    {
      "id": "4aadb2915bbec45f",
      "url": "https://imrafaeldev.site/projects/sms-manager-en",
      "title": "SMS Manager",
      "content": "## Problem\n\nSMS campaigns start from CSV files, users, companies, and authentication between services. Validating and persisting in the same process that fires thousands of messages couples the API to the pace of the carrier and the queue.\n\n## Constraints\n\nThe environment must be reproducible. Authentication between services cannot depend on JWT without revocation. Consumers in more than one language exist to compare the same messaging contract.\n\n## Decision\n\nSeven applications: NestJS user/auth and company/campaign APIs; equivalent consumers in Node.js/TypeScript, Go, and Rust; declarative provisioner for exchanges, queues, and bindings; CSV bulk generator. PostgreSQL/TypeORM for relational API data; MongoDB for consumption results; Redis for opaque revocable token cache; gRPC for authentication between services; RabbitMQ with topic exchanges for batches. The API publishes and moves on without blocking on the carrier pace.\n\n## Current state\n\nPublic repository with Docker Compose for PostgreSQL, MongoDB, Redis, and RabbitMQ. The architecture separates domain, application, and infrastructure in the users, authentication, and companies contexts; the three consumers implement the same message contract.\n\n## Limitations\n\nIt is a study artifact and local messaging operation, not a commercial product with a carrier SLA. The three consumers demonstrate the contract; they do not claim the three languages run together in the author's production.",
      "description": "Decoupled SMS campaigns: the Nest API persists and publishes, the queue delivers, and consumers in TypeScript, Go, or Rust record the result. Opaque token, Redis, and gRPC between services.",
      "keywords": [
        "authentication",
        "consumers",
        "between",
        "services",
        "same",
        "carrier",
        "contract",
        "with",
        "three",
        "users"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "projeto-sms-manager",
        "slug": "sms-manager",
        "title": "SMS Manager",
        "description": "Decoupled SMS campaigns: the Nest API persists and publishes, the queue delivers, and consumers in TypeScript, Go, or Rust record the result. Opaque token, Redis, and gRPC between services.",
        "excerpt": "Importing CSV must not block the Nest API. The campaign persists and publishes to RabbitMQ; consumers in TypeScript, Go, or Rust record the result in Mongo. The API does not wait for the carrier.",
        "repoUrl": "https://github.com/imrafaeldev/sms-manager",
        "status": "Public repository",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/sms-manager-en.md"
      }
    },
    {
      "id": "4b9ab3a81aed8207",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-es",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 2)",
      "content": "No era solo esperar base, API o evento externo. Era cálculo sobre un buffer grande de apuestas.\n\nEse es el tipo de escenario en que `Promise.all` puede engañar. Da sensación de paralelismo, pero no transforma automáticamente trabajo pesado de CPU en ejecución paralela real. Si las tareas son cómputo intenso y corren en el mismo hilo principal, el Event Loop queda ocupado.\n\nEl resultado puede ser peor de lo esperado: bloqueo del loop, aumento de latencia, peor responsividad y mayor presión sobre CPU y memoria.\n\nEl problema no era que Node.js fuera malo. El problema era usar el modelo estándar de Node para una carga que exigía otro tipo de ejecución.\n\n## Donde Go encajó mejor\n\nLa solución fue reescribir ese proceso en Go usando goroutines. La idea era dividir el cálculo en chunks menores, procesar esos pedazos en paralelo y sincronizar solo al final.\n\nEse diseño encajaba mejor en el problema porque el trabajo era CPU bound y podía dividirse. En lugar de un flujo centralizado intentando coordinar varias operaciones pesadas, el procesamiento pasó a distribuirse en unidades menores de ejecución.\n\nCon un worker pool, por ejemplo, se puede controlar el número de goroutines, limitar el fan-out, usar mejor los cores disponibles y evitar que el sistema dispare trabajo sin límite.\n\nLa ganancia apareció. El tiempo total cayó cerca del 25%. El proceso que quedaba en torno a los 30 segundos pasó a correr en algo cercano a 22,5 segundos. También hubo mejor uso de CPU.\n\nPero la parte importante de la historia no es \"Go lo resolvió\". La parte importante es que Go resolvió un lado del problema y reveló otro.\n\n## El cuello de botella puede cambiar de lugar\n\nLa primera dificultad fue garantizar que el resultado en Go fuera igual al resultado en Node.js. Esto es menos glamoroso que hablar de concurrencia, pero es lo que separa la optimización real de la regresión enmascarada.\n\nSi el cálculo se vuelve más rápido y cambia el resultado financiero, la mejora no vale nada.",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "más",
        "tiempo",
        "mismo",
        "trabajo",
        "loop",
        "problema",
        "para",
        "resultado"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia",
        "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
        "excerpt": "Node.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-es.md"
      }
    },
    {
      "id": "4c3d64c0e7341636",
      "url": "https://imrafaeldev.site/articles/go-intensive-en",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 4)",
      "content": "## Kubernetes, observability, and performance\n\nOn `SIGTERM`, remove readiness, stop fetching work, drain in-flight work within the grace period, ACK only completed messages, and close producers, connections, and telemetry last. Liveness asks whether the process progresses; it should not depend on every external service. Readiness asks whether this pod can accept work now.\n\nFor consumer autoscaling, CPU alone is weak. Watch lag, age of the oldest message, arrival rate, processing time, and worker-pool occupancy.\n\nUse structured logs and correlation fields without logging credentials or full sensitive payloads. Track throughput, errors by class, p50/p95/p99, lag, event age, retries, DLQ volume, duplicates, goroutines, heap, and GC pauses. Use sampled traces across ingestion, stream, and persistence; tracing every high-frequency reading can cost more than it helps.\n\nA data race is concurrent access to one memory location with at least one write and no synchronization order. Channel sends, mutex unlock/lock, and atomic operations create useful ordering. The detector only covers executed paths:\n\n```bash\ngo test -race ./...\ngo test -bench=. -benchmem ./...\ngo tool pprof cpu.out\ngo tool trace trace.out\n```\n\nG is a goroutine, M an OS thread, and P a logical execution resource. `GOMAXPROCS` limits Ps that run Go code concurrently, not the goroutine count. Goroutines are lightweight, not free. Profile before pooling or micro-optimizing. Preallocate known slice capacity, avoid repeated `string`/`[]byte` conversions in hot paths, and treat `sync.Pool` as an opportunistic temporary-object cache.\n\n## Interview answers to keep ready\n\n**Is a goroutine a thread?** No. It is a lightweight runtime-managed execution unit multiplexed over OS threads.\n\n**Channel or mutex?** Use a channel to transfer work or ownership; use a mutex to protect shared state and invariants.\n\n**Who closes a channel?** The producer that knows there will be no more sends.",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "context",
        "reading",
        "channel",
        "https",
        "errors",
        "device",
        "with",
        "time",
        "return",
        "goroutine"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-go-intensive",
        "slug": "go-intensive",
        "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes",
        "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
        "excerpt": "A 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "articles/go-intensive-en.md"
      }
    },
    {
      "id": "4ca8cb0670c4ebfa",
      "url": "https://imrafaeldev.site/artigos/backend-performance-perto-dos-dados",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 2)",
      "content": "Foi o que aconteceu em um dashboard em que trabalhei. Ele carregava de forma síncrona, e uma única consulta fazia tudo de uma vez: buscava os dados, cruzava várias informações e calculava os valores exibidos. Como a CPU do banco ficava muito alta, quase dobramos CPU e RAM.\n\nO banco ficou maior. O dashboard continuou passando de um minuto para carregar. A mesma consulta ainda concentrava todo o trabalho pesado em uma única execução.\n\nFoi quando abrimos o plano de execução. A tela devolvia poucos indicadores, mas a consulta atravessava relacionamentos, formava um volume intermediário grande e gastava CPU em agregações antes de chegar neles. A resposta final era pequena. O trabalho para produzi-la, enorme.\n\nDobrar CPU e RAM tinha dado mais fôlego ao banco, mas a busca continuava obrigando-o a fazer tudo de uma vez. O plano mostrou que a investigação precisava entrar no caminho percorrido pela consulta.\n\n## O que preciso ver antes de mexer no banco\n\nPara o banco virar suspeito de verdade, quero o plano de execução e as métricas apontando nessa direção. Tempo da consulta, leituras e cardinalidade geralmente confirmam ou eliminam uma hipótese em poucos minutos.\n\nSe essas medidas estiverem saudáveis, tiro o banco da frente. Já peguei API lenta com a consulta respondendo dentro do esperado, enquanto o atraso estava no processamento da aplicação e em chamadas remotas feitas de forma sequencial. A partir daí, continuar procurando um defeito no banco seria apenas insistir na camada errada.\n\nAntes de aprofundar, passo um radar curto pela consulta e pelo código. Uma query para carregar a lista seguida de outra para cada registro pede uma contagem de consultas. Esse N+1 aparece com frequência quando o ORM deixa as relações para o backend buscar uma a uma.\n\nUm JOIN que multiplica linhas antes da agregação pede a medição do volume intermediário. Índice ausente pede plano. Um filtro que aplica uma função sobre a coluna indexada também.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "antes",
        "execução",
        "dados",
        "não",
        "trabalho"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/artigos/backend-performance-perto-dos-dados"
      }
    },
    {
      "id": "4d4e2e2e932a0f7e",
      "url": "https://imrafaeldev.site/experiences/infosistemas-es",
      "title": "Infosistemas | Rafael Pereira",
      "content": "## Contexto\n\nEn Infosistemas, trabajé en plataformas para arrendadoras, flotas, automotrices y operaciones de movilidad. Era un entorno enterprise de integraciones, flujos fiscales y servicios de alto volumen, donde trabajé junto a DevOps, SREs y DBAs.\n\n## Cómo trabajé\n\nMi trabajo combinó arquitectura y ejecución práctica. Rediseñé flujos entre microservicios, lideré integraciones en NestJS y Go e implementé trazabilidad de eventos críticos con NestJS y MongoDB. También evolucioné APIs, investigué problemas de seguridad y contribuí a jornadas digitales y componentes web cuando era necesaria la continuidad entre backend e interfaz.\n\nEl principio que orientó mi trabajo fue hacer observables y tratables las fallas desde el diseño. En mensajería, traté durabilidad, retry, idempotencia y control de consumo como partes del flujo, no como correcciones posteriores.\n\n## Lo que me llevé\n\nEsta experiencia amplió mi trabajo en sistemas con muchas dependencias y especialistas. Aprendí a convertir requisitos, riesgos y restricciones operativas en decisiones que siguieran claras hasta la validación con el cliente.",
      "description": "Mi experiencia en Infosistemas con mensajería, integraciones y plataformas de movilidad.",
      "keywords": [
        "trabajé",
        "trabajo",
        "integraciones",
        "flujos",
        "entre",
        "nestjs",
        "como",
        "contexto",
        "infosistemas",
        "plataformas"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-infosistemas",
        "slug": "infosistemas",
        "title": "Infosistemas | Rafael Pereira",
        "description": "Mi experiencia en Infosistemas con mensajería, integraciones y plataformas de movilidad.",
        "company": "Infosistemas",
        "role": "Ingeniero de Software Senior / Arquitecto de Software",
        "period": "feb/2025 a may/2026",
        "caseSlug": "mensajeria-rabbitmq",
        "caseSummary": "El caso detalla cómo rediseñé la mensajería RabbitMQ para hacer más previsibles los flujos críticos y reducir fallas intermitentes entre microservicios.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/infosistemas-es.md"
      }
    },
    {
      "id": "4e895e52da4dc725",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 7)",
      "content": "*Originally published on [LinkedIn](https://www.linkedin.com/pulse/maioria-dos-problemas-de-performance-backend-come%C3%A7a-perto-s-pereira-zqlae/) on July 16, 2026.*",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "4e8bb4a93dfe45cd",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-pt-br",
      "title": "Desenferrujando a lógica #01: Group Anagrams (Part 2)",
      "content": "1. `sortString(str)` transforma a string em bytes, ordena os caracteres e devolve uma nova string.\n2. `sortedStr := sortString(str)` produz a chave daquele termo.\n3. `mapping[sortedStr] = append(...)` usa essa chave para acumular os anagramas no mesmo grupo.\n4. Para `eat`, `tea` e `ate`, `sortedStr` será sempre `aet`.\n\nO mapa começa a ficar assim:\n\n```text\n\"aet\" -> [\"eat\", \"tea\", \"ate\"]\n\"ant\" -> [\"tan\", \"nat\"]\n\"abt\" -> [\"bat\"]\n```\n\nCalculo uma chave e adiciono a palavra diretamente ao grupo correspondente. O `map` evita comparar cada string com todas as outras.\n\n## Onde está o custo dessa abordagem?\n\nPara descobrir a chave, preciso ordenar cada string. Se uma string tem `k` caracteres, essa ordenação custa aproximadamente `O(k log k)`. Repetindo para `n` strings, a parte dominante fica em `O(n × k log k)`.\n\n### Por que retirar o sort?\n\nPara `eat`, eu ordenava os caracteres para chegar em `aet`. Para `tea`, fazia outra ordenação para chegar no mesmo `aet`. O `sort` funciona porque cria uma representação comum. Só que o problema não exige ordenar nada.\n\nPara saber se duas strings são anagramas, basta verificar se possuem a mesma quantidade de cada letra.\n\n## Essa observação muda a solução\n\nEm vez de colocar as letras na mesma ordem, posso contar quantas vezes cada uma aparece. Nesse exercício, as entradas usam letras minúsculas de `a` até `z`. Cada string pode ser representada por 26 contadores.\n\n`tea` e `ate` produzem a mesma contagem. Não preciso reorganizar nenhum caractere. Só percorro a string e conto as ocorrências. Para `eat`, a parte relevante fica:\n\n```text\na = 1\ne = 1\nt = 1\n```\n\n### Solução usando contagem\n\n`var key [26]uint8` cria 26 posições, uma para cada letra de `a` até `z`. `key[str[i]-'a']++` encontra a posição de cada caractere e incrementa o contador. Depois, `groups[key] = append(groups[key], str)` usa o próprio vetor de frequências como chave do grupo.",
      "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "chave",
        "não",
        "solução",
        "result",
        "problema",
        "groups",
        "group"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "desenferrujando-logica-group-anagrams",
        "title": "Desenferrujando a lógica #01: Group Anagrams",
        "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
        "excerpt": "Eu não tinha parado de escrever código. O que mudou foi terceirizar partes do raciocínio. Group Anagrams foi o exercício para recuperar o hábito.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-pt-br.md"
      }
    },
    {
      "id": "4f7b1a3013649ab3",
      "url": "https://imrafaeldev.site/articles/go-intensive-en",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 3)",
      "content": "Backpressure is a product and operations policy. When ingestion accepts 50,000 messages per second and persistence completes 20,000, storing the difference in memory only moves the incident. Decide whether to block producers, reject with retry, pause consumption so the broker holds durable backlog, discard stale samples, aggregate, or spill to disk. Define queue size, occupancy metric, timeout, and saturation action.\n\n## A resilient IoT pipeline\n\n```text\ndevice -> MQTT/broker -> Go ingestion -> stream -> processors -> storage\n                              \\-> DLQ       \\-> current state\n```\n\nMQTT fits device connectivity. A stream such as Kafka fits durable retention, replay, and internal partitioning. gRPC is typed internal RPC; WebSocket updates dashboards. These protocols solve different boundaries.\n\nMultiple workers break global ordering. Telemetry commonly needs order per device, so partition by a stable key such as `hash(device_id) % N` and process each partition sequentially. Keep both `observed_at` and `ingested_at`, plus `sequence`, `event_id`, and `boot_id` where applicable. Device clocks drift and restart.\n\nTreat the end-to-end path as *at least once*. Receive the event, validate its envelope and schema version, check an idempotency key, persist the effect and deduplication marker in one transaction where possible, then ACK. A transactional outbox closes the gap between committing database state and publishing a following event. Consumers still need idempotency because duplicates remain possible.\n\nRetry only transient failures. Invalid payloads and rejected business rules do not improve with another attempt. Add a limit, a total budget, and jitter so replicas do not retry together.\n\nAt the edge, use TLS, per-device identities, topic authorization, strict payload limits, and validation before allocating large structures. Rotation, revocation, sequence numbers, nonces, and time windows matter when the business protocol must resist replay.",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "context",
        "reading",
        "channel",
        "https",
        "errors",
        "device",
        "with",
        "time",
        "return",
        "goroutine"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-go-intensive",
        "slug": "go-intensive",
        "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes",
        "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
        "excerpt": "A 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "articles/go-intensive-en.md"
      }
    },
    {
      "id": "509be054ebfcf76d",
      "url": "https://imrafaeldev.site/en/articles/go-intensive",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 3)",
      "content": "errgroup combines waiting, first-error propagation, and shared cancellation. Set a limit for independent tasks:\n\nfunc ProcessBatch(ctx context.Context, batch []Reading) error {\ng, ctx := errgroup.WithContext(ctx)\ng.SetLimit(16)\n\nfor _, reading := range batch {\nreading := reading\ng.Go(func() error {\nreturn processOne(ctx, reading)\n})\n}\nreturn g.Wait()\n}\nClassify errors before acting. A database outage can cancel a batch. Invalid, duplicate, or schema-incompatible messages belong in quarantine or a DLQ, not in a failure that stops the whole consumer.\n\nUse atomic for an independent flag or counter, sync.Mutex for an invariant across fields, and a channel for work transfer. Do not copy a mutex after first use or keep a lock during remote I/O.\n\nBackpressure is a product and operations policy. When ingestion accepts 50,000 messages per second and persistence completes 20,000, storing the difference in memory only moves the incident. Decide whether to block producers, reject with retry, pause consumption so the broker holds durable backlog, discard stale samples, aggregate, or spill to disk. Define queue size, occupancy metric, timeout, and saturation action.\n\n## A resilient IoT pipeline\n\ndevice -> MQTT/broker -> Go ingestion -> stream -> processors -> storage\n\\-> DLQ \\-> current state\nMQTT fits device connectivity. A stream such as Kafka fits durable retention, replay, and internal partitioning. gRPC is typed internal RPC; WebSocket updates dashboards. These protocols solve different boundaries.\n\nMultiple workers break global ordering. Telemetry commonly needs order per device, so partition by a stable key such as hash(device_id) % N and process each partition sequentially. Keep both observed_at and ingested_at, plus sequence, event_id, and boot_id where applicable. Device clocks drift and restart.",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "reading",
        "context",
        "channel",
        "errors",
        "return",
        "value",
        "device",
        "goroutine",
        "work",
        "articles"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/en/articles/go-intensive"
      }
    },
    {
      "id": "511969a510b2bfcc",
      "url": "https://imrafaeldev.site/experiencias/flapper",
      "title": "Flapper",
      "content": "- [Início](/)\n- Flapper\n\n## Flapper\n\nMinha experiência na Flapper com modernização incremental de um legado em produção.\n\nCargo\n\nEngenheiro de Software Full Stack\n\nPeríodo\n\nset/2021 a jun/2022\n\n## O contexto\n\nNa Flapper, trabalhei em uma plataforma de aviação executiva cujo produto principal era um monólito PHP com mais de sete anos, pouca documentação útil e sem os desenvolvedores originais disponíveis para explicar o sistema.\n\n## Como atuei\n\nO desafio não era trocar PHP por TypeScript. A aplicação estava em produção e sustentava o negócio, então comecei pela descoberta do domínio. Usei o banco de dados para mapear relações, identificar bounded contexts e planejar uma migração incremental baseada no padrão Strangler.\n\nMigrei módulos como pessoas, autenticação e aeronaves para serviços em Node.js, NestJS e Go. Para reduzir dependências relacionais entre contextos, trabalhamos com projeções locais e eventos no Kafka; gRPC, REST e GraphQL foram aplicados conforme a necessidade de cada integração.\n\n## O que levo\n\nFoi uma experiência que consolidou minha visão de modernização de legado: tecnologia vem depois de entender fronteiras, riscos e uma sequência que preserve a operação. Além de entregar módulos, documentei decisões e conduzi workshops para que o time pudesse continuar a transformação.\n\n## Case relacionado\n\nO case mostra como investiguei o domínio a partir de banco e código e conduzi a modernização incremental do monólito sem interromper a operação.\n\n[Ler case completo](/casos/modernizacao-monolito-sem-documentacao/)",
      "description": "Minha experiência na Flapper com modernização incremental de um legado em produção.",
      "keywords": [
        "para",
        "flapper",
        "modernização",
        "incremental",
        "como",
        "case",
        "minha",
        "experiência",
        "legado",
        "produção"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/flapper"
      }
    },
    {
      "id": "51b4885f689d0b4d",
      "url": "https://imrafaeldev.site/en/cases/external-sql-server-analytics",
      "title": "VBET: analytics on top of a SQL Server we could not change (Part 1)",
      "content": "- [Home](/en/)\n- [Cases](/en/cases/)\n- VBET: analytics on top of a SQL Server we could not change\n\nVBET\n\n## VBET: analytics on top of a SQL Server we could not change\n\nThe database belonged to another team. The dashboard had to stop depending on a schema we did not control.\n\nSenior Backend Engineer Oct/2023 – Feb/2025\n\n~7 min → <1 s commissions, from the original load to the warm cache\n\nPipeline de comissões\nCada estágio corresponde a uma decisão incremental documentada no case. Números de outras histórias não entram neste desenho.\n\n- [01Context](#context)\n- [02Constraints](#constraints)\n- [03Problem](#problem)\n- [04Decision](#decision)\n- [05Discarded alternative](#discarded-alternative)\n- [06Result](#result)\n- [07Limitations](#limitations)\n\n## Context\n\nAt VBET, between October 2023 and February 2025, the analytics product served iGaming influencers and affiliates. The dashboard gathered dozens of metrics; commission was the most critical reading. Influencers accepted a small lag in same-day data as long as the screen responded. Payout depended on consolidated prior-day data, not on the live value.\n\n## Constraints\n\nThe SQL Server was external, shared, and not reliably modifiable. Temporary indexes could be removed by the database owner. The original API mixed SQL queries built from parameters, with injection risk, and aggregated too much in memory.\n\n## Problem\n\nThe system had been sized for smaller influencers. With larger bases, the worst dashboard peak reached about seven minutes. Security and maintainability came before performance: raw queries, little test coverage, and a synchronous path that recalculated too much on every request.\n\n## Decision\n\nThe evolution was incremental, in the order the constraints appeared:\n\n- remove unsafe SQL, parameterize access, document, and test;\n\n- optimize queries and temporary indexes, as mitigation rather than invariant;\n\n- parallelize independent queries with Go, goroutines, and channels;",
      "description": "Commission dashboard at VBET: external SQL Server, owned ETL, and cache. The measured line went from about seven minutes at peak to under one second with a warm cache.",
      "keywords": [
        "with",
        "about",
        "queries",
        "imrafaeldev",
        "vbet",
        "server",
        "database",
        "dashboard",
        "from",
        "cache"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/cases/external-sql-server-analytics"
      }
    },
    {
      "id": "521b2a2940713850",
      "url": "https://imrafaeldev.site/en/articles/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 3)",
      "content": "The first difficulty was guaranteeing the Go result equaled the Node.js result. That is less glamorous than talking about concurrency, but it is what separates real optimization from masked regression.\n\nIf the calculation gets faster and changes the financial result, the improvement is worthless.\n\nAfter that, the main problem became chunk splitting. The initial strategy consumed too much memory. In local tests, with smaller datasets, proportional growth reached near 15% at some points.\n\nThe discomfort came from a wrong expectation: I thought moving to Go would automatically solve the problem. In practice, I had only moved the bottleneck. Before, the limit was clearer on CPU. After, the partitioning strategy started pressuring memory.\n\nThat can happen for several reasons: unnecessary copies, large buffers, slices holding references to bigger arrays, oversized internal queues, or too much work prepared before being processed.\n\nIn the final result, memory still grew about 5%. In that case, the time and CPU gain compensated the loss. But that is not a universal rule. If the production load were much larger, or if the service ran with a thin memory margin, that trade-off could stop being acceptable.\n\nParallelism costs coordination, allocation, synchronization, and observability. There is no free parallel execution.\n\n## When I would keep Node.js\n\nI would keep Node.js without hesitation for I/O orchestration: WebSocket communication, calls to several APIs, database queries, event dispatch, integration between services, and flows where dead time is waiting.\n\nIn those cases, the Event Loop is an excellent choice. It allows high volumes of concurrent operations without creating one thread per request. For event-oriented applications, that is simple, productive, and easy to fit into the JavaScript ecosystem.\n\nThe mistake is pushing that same model onto heavy computation and thinking I/O concurrency becomes CPU parallelism. It does not.\n\n## When I would look at Go",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "loop",
        "event",
        "problem",
        "work",
        "goroutines",
        "same",
        "concurrency"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/en/articles/goroutines-vs-event-loop"
      }
    },
    {
      "id": "53d3fd891e9c0938",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 2)",
      "content": "- O zero value costuma ser utilizável. Prefira tipos cujo estado inicial seja válido quando isso não esconder uma regra de negócio.\n- Slice é uma visão sobre um array. Cópias podem compartilhar o mesmo backing array; `append` pode reutilizá-lo ou alocar outro.\n- `map` não tem ordem de iteração e não suporta leitura e escrita concorrentes sem sincronização.\n- `string` contém bytes imutáveis, normalmente UTF-8. `len` conta bytes; `range` decodifica runes.\n- `defer` executa em LIFO, mas avalia os argumentos quando é registrado.\n\nInterfaces são satisfeitas implicitamente. Defina a interface pequena no pacote que a consome, em vez de exportar um contrato grande ao lado da implementação. Erros são valores: acrescente contexto com `%w` e inspecione a causa com `errors.Is` ou `errors.As`.\n\n```go\nif err := reading.Validate(); err != nil {\n\tif errors.Is(err, ErrOutOfRange) {\n\t\treturn sendToDLQ(reading, err)\n\t}\n\treturn fmt.Errorf(\"validate reading: %w\", err)\n}\n```\n\n`panic` fica para invariantes quebradas ou falha irrecuperável de inicialização. `recover` só alcança um panic na mesma goroutine e pertence a fronteiras controladas, como middleware.\n\n## Goroutines, channels e cancelamento\n\nUma goroutine não é uma thread dedicada. O runtime a agenda sobre threads do sistema operacional. Iniciar uma goroutine sem saber quem a cancela ou espera cria risco de leak.\n\nChannels transportam trabalho ou propriedade. Um mutex protege estado compartilhado. Um buffer apenas absorve uma diferença temporária de velocidade; não cria capacidade infinita.\n\n```go\njobs := make(chan Reading, 128)\n\ngo func() {\n\tdefer close(jobs) // quem produz fecha\n\tfor _, reading := range batch {\n\t\tjobs <- reading\n\t}\n}()\n\nfor reading := range jobs {\n\tif err := process(reading); err != nil {\n\t\t// tratar ou registrar o erro da mensagem\n\t}\n}\n```",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "5549463610707649",
      "url": "https://imrafaeldev.site/es",
      "title": "Rafael Pereira, ingeniero de software sénior (Part 1)",
      "content": "Rafael Pereira / ingeniero de software / sistemas\n\n## Trabajo en los puntos en los que los sistemas simples *dejan de ser simples*.\n\nEstudios de caso, proyectos y textos técnicos sobre escala, fallos, legado y reglas de negocio. Cada pieza muestra la restricción, la decisión y hasta dónde llega la solución.\n\n[Ver los estudios de caso](/es/casos/) [Ver opciones de contacto](/es/contacto/)\n\n[Case en foco **Infosistemas** ~98% · reducción de fallos intermitentes en los flujos críticos Abrir el case](/es/casos/mensajeria-rabbitmq/) [**VBET** ~7 min → <1 s](/es/casos/analitica-sql-server-externo/)\n\n- [~98% Infosistemas: reducción de fallos intermitentes en los flujos críticos](/es/casos/mensajeria-rabbitmq/)\n- [~7 min → <1 s VBET: comisiones, de la carga original a la caché caliente](/es/casos/analitica-sql-server-externo/)\n- [~25% Flapper: menos tablas en la separación por dominios](/es/casos/modernizacion-monolito-sin-documentacion/)\n\nSeñal\n\nMétodo\n\n## La restricción viene antes del diagrama.\n\nEmpiezo por lo que ocurre cuando un mensaje falla.\n\nLa base de datos no puede cambiar. El legado no puede parar.\n\nEl camino feliz viene después.\n\nLa decisión registra la alternativa descartada y la condición que justificaría revisarla.\n\nEl código muestra qué se hizo; el case muestra por qué.\n\nPrueba\n\n## Tres sistemas, tres restricciones\n\nInfosistemas feb/2025 – may/2026\n\n~98% reducción de fallos intermitentes en los flujos críticos\n\n### [Infosistemas: contrato de fallo en la mensajería RabbitMQ](/es/casos/mensajeria-rabbitmq/)\n\nFallos intermitentes entre microservicios sin contrato para retry, DLQ o duplicidad. Más consumidores solo desplazaban la sobrecarga.\n\n[Abrir el case](/es/casos/mensajeria-rabbitmq/)\n\nContrato de falha na mensageria\nPublicação confirmada, consumo com prefetch controlado, retry com backoff e DLQ por fluxo. Escalar só consumidores fica de fora do desenho.\n\n[Ver o caso](/es/casos/mensajeria-rabbitmq/)\n\nVBET oct/2023 – feb/2025",
      "description": "Portafolio institucional y hub editorial de Rafael Pereira. Trabajo en los puntos en los que los sistemas simples dejan de ser simples.",
      "keywords": [
        "casos",
        "proyectos",
        "abrir",
        "case",
        "qué",
        "caso",
        "fallos",
        "decisión",
        "contacto",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/es"
      }
    },
    {
      "id": "5559087a4a455c7b",
      "url": "https://imrafaeldev.site/en/cases/external-sql-server-analytics",
      "title": "VBET: analytics on top of a SQL Server we could not change (Part 2)",
      "content": "- when the bottleneck moved back to SQL Server, build an owned ETL and PostgreSQL, with pre-computation, checkpoints, and reconciliation;\n\n- separate REALTIME reads (trend, eventual consistency) from CLOSED reads (financial accuracy and payout);\n\n- cache-aside with TTL aligned to the accepted lag of about five minutes;\n\n- controlled degradation if the cache failed, instead of taking the screen down.\n\n## Discarded alternative\n\nInsisting on indexes in the external database as architecture, or recalculating years of history on every access. Also discarded: paying affiliates from REALTIME data.\n\n## Result\n\nThe reconciled line in the experience dossier, for the commission/dashboard path, is:\n\n- initial worst peak: about 7 minutes;\n\n- after queries and indexes: about 3 minutes;\n\n- after parallelization: about 1 minute;\n\n- after ETL/PostgreSQL: about 15 seconds at commission p99 without cache;\n\n- warm cache: under 1 second.\n\nEach number belongs to its stage. It does not describe the gain of a later decomposition into microservices.\n\n## Limitations\n\nThe investigation, the ETL + owned database decision, the REALTIME/CLOSED split, and the degradation policy are the attributable core here. Decomposing the monolith into Kubernetes is a separate story and does not mix the “500%” nor sub-60 ms latency into this case. Older resume versions citing 30 seconds on cold load, 11 seconds, or SLA percentages without a scenario are left out. If the product starts requiring realtime accuracy for payout, the CLOSED split stops being enough.\n\nContact\n\n## Dealing with a system that's stopped being simple?\n\nReach out directly via @imrafaeldev, no form — for professional conversation, start on LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Open the contact page](/en/contact/)",
      "description": "Commission dashboard at VBET: external SQL Server, owned ETL, and cache. The measured line went from about seven minutes at peak to under one second with a warm cache.",
      "keywords": [
        "with",
        "about",
        "queries",
        "imrafaeldev",
        "vbet",
        "server",
        "database",
        "dashboard",
        "from",
        "cache"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/cases/external-sql-server-analytics"
      }
    },
    {
      "id": "5680f67918f82160",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 9)",
      "content": "// core/protocols/UpdateUserProtocol.ts\nexport abstract class UpdateUserProtocol {\n  abstract update(id: string, name: string): Promise<UserEntity>;\n}\n\n// core/protocols/DeleteUserProtocol.ts\nexport abstract class DeleteUserProtocol {\n  abstract delete(id: string): Promise<void>;\n}\n```\n\n## Conclusión\n\nSeparar Core, Adapters e Infra deja la regla de negocio testeable sin Nest, Prisma o HTTP. Cambiar base o canal de entrada se vuelve cambio de adapter y wiring de DI — no reescritura del usecase. El costo es más archivos y disciplina de frontera; la ganancia aparece cuando el sistema necesita cambiar sin arrastrar el dominio.\n\nPublicado originalmente en [Medium](https://medium.com/@contato.dev.rafael.pereira/typescript-cleanarch-668935d677c2) (15/03/2023).",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 8,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "57a8b41b00c98e2e",
      "url": "https://imrafaeldev.site/en/resume",
      "title": "Resume (Part 4)",
      "content": "Worked on laboratory, research, and development systems, combining maintenance of an existing product with functional evolution.\n\nContribution\n\nMaintained and evolved the system with Java, Spring Boot, and Angular, added integration tests, and delivered reports and end-to-end features.\n\n[Read the full experience](/en/experience/sustentec/)\n\n- Nov 2019 to Jan 2021 Campina Grande, Paraíba / Remote Braistech Mid-level Full Stack Software Engineer Expand experience Collapse experience\nLed the structure of the main system with Node.js and NestJS and designed microservices for the business core.\n\nContext\n\nIn a product involving crypto-asset contracts and financial movements, took broad responsibility within a small team.\n\nContribution\n\nLed the structure of the main system with Node.js and NestJS, designed microservices for the business core, helped evolve the code toward Clean Architecture, and mentored junior developers.\n\n[Read the full experience](/en/experience/braistech/)\n\n## Specialties\n\n-\n\n### Node.js\n\nSpecialized in APIs, microservices, and integrations with NestJS.\n\n-\n\n### Go\n\nAdvanced experience with APIs, concurrency, and high-volume processing.\n\n-\n\n### Java\n\nExperience maintaining and evolving systems with Spring Boot.\n\n-\n\n### React\n\nWorked on the evolution and maintenance of React applications.\n\n-\n\n### Angular\n\nWorked on an Angular web application with forms, validation, and API consumption.\n\n## Education\n\n-\n\n### Systems Analysis and Development\n\nTechnology degree · UNOPAR — Universidade Norte do Paraná\n\ncompleted in 2024\n-\n\n### Cloud Computing\n\nGraduate degree · Anhanguera Educacional\n\ncompleted in 2025\n\n## Public links\n\n- [LinkedIn — Open public link](https://www.linkedin.com/in/this-rafael-pereira/)\n- [GitHub — Open public link](https://github.com/this-rafael)",
      "description": "Rafael Pereira",
      "keywords": [
        "experience",
        "with",
        "nestjs",
        "full",
        "engineer",
        "node",
        "backend",
        "remote",
        "expand",
        "collapse"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/en/resume"
      }
    },
    {
      "id": "58ee22d1dd16f4a4",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 5)",
      "content": "```typescript\nexport const mockDbConnector: DbConnector = {\n  users: {\n    getById: async (id: string) =>\n      Promise.resolve(new UserEntity({ id, name: \"Test\" })),\n    getByName: async (name: string) =>\n      Promise.resolve(new UserEntity({ id: \"1\", name })),\n    register: async (name: string) =>\n      Promise.resolve(new UserEntity({ id: \"2\", name })),\n    update: async (id: string, name: string) =>\n      Promise.resolve(new UserEntity({ id, name })),\n    delete: async (_id: string) => Promise.resolve(),\n  },\n  profiles: {\n    getById: async (_id: string) => Promise.resolve(null),\n    getByName: async (_name: string) => Promise.resolve(null),\n    register: async (_name: string) => Promise.resolve(null),\n    update: async (_id: string, _name: string) => Promise.resolve(null),\n    delete: async (_id: string) => Promise.resolve(),\n  },\n};\n```\n\n`UserEntity` (Core) não é a tabela do banco. São coisas diferentes.\n\n**“Implementar vários protocols fere o S do SOLID?”** Depende. Separar cada protocol em classe própria maximiza SRP. Manter um repository por entidade/agregado também é coerente: o escopo é “dados de User neste conector”. Critérios práticos para *não* juntar A e B na mesma classe:\n\n1. Implementar B exigiria outra dependência externa.\n2. A e B trabalham entidades/escopos diferentes.\n3. Complexidade ciclomática (ou cognitiva) sobe demais.\n4. A classe passa de ~500 linhas — limiar subjetivo; use senso de time.\n\nCom DI e segregação de interface, `CreateUserUsecase` só conhece `CreateUserProtocol` e `GetUserByNameProtocol`. Em runtime pode receber a mesma instância de `UsersMockRepository` nos dois parâmetros — sem acoplar à classe concreta.\n\n### Services\n\nServices adaptam chamadas da Infra para o Core: REST → service → usecase → protocol → repository → banco. O mesmo desenho vale para handler de fila ou resolver GraphQL: entrada diferente, service no meio.",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "59c66a81e56b4fed",
      "url": "https://imrafaeldev.site/es/casos/modernizacion-monolito-sin-documentacion",
      "title": "Flapper: descubrir el dominio antes de separar el monolito (Part 2)",
      "content": "- mantener en cada contexto solo la representación local de los datos que necesitaba;\n\n- propagar cambios por eventos en Kafka, en lugar de conectar todos los servicios a la base antigua;\n\n- usar Node.js, NestJS y Go en los primeros módulos, con gRPC, REST o GraphQL según el consumidor;\n\n- documentar la estrategia y los primeros módulos para que el equipo pudiera continuar la transformación.\n\n## Alternativa descartada\n\nReescribir el monolito entero, o mantener servicios nuevos atados a la misma base y a las mismas relaciones compartidas. La primera opción pararía el negocio; la segunda preservaría el acoplamiento que la migración necesitaba reducir.\n\n## Resultado\n\nLa separación por dominios redujo en aproximadamente el 25% el número de tablas y creó siete bases organizadas por contexto. Los módulos de personas, autenticación y aeronaves fueron los primeros pasos de una transformación planeada para continuar más allá de la entrega inicial.\n\n## Limitaciones\n\nEl número mide la reducción de tablas en esa separación de dominios, no una ganancia financiera, la migración completa o un resultado de mercado de la empresa. La fecha final de la experiencia tiene divergencia histórica en fuentes antiguas; el período publicado sigue el perfil exportado. Si un dominio aún depende de reglas no mapeadas en el monolito, su extracción debe posponerse o recibir una integración de transición explícita.\n\nContacto\n\n## ¿Tienes un sistema que dejó de ser simple?\n\nEscríbeme directo por @imrafaeldev, sin formulario — para conversación profesional, empieza en LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir la página de contacto](/es/contacto/)",
      "description": "Modernización incremental de un monolito PHP sin documentación en Flapper: base como fuente de descubrimiento, bounded contexts y Strangler. La separación por dominios redujo en aproximadamente el 25% el número de tablas.",
      "keywords": [
        "monolito",
        "para",
        "dominio",
        "antes",
        "base",
        "imrafaeldev",
        "flapper",
        "tablas",
        "contexto",
        "migración"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/es/casos/modernizacion-monolito-sin-documentacion"
      }
    },
    {
      "id": "5c318cd8325a1fa5",
      "url": "https://imrafaeldev.site/experiences/maxmilhas-es",
      "title": "Maxmilhas | Rafael Pereira",
      "content": "## Contexto\n\nEn Maxmilhas, trabajé en un período corto e intenso, en flujos posventa de viajes relacionados con cancelaciones, cambios, cupones y comunicación con clientes.\n\n## Cómo trabajé\n\nDesarrollé microservicios en Node.js, NestJS y Elixir para automatizar procesos que aún dependían de intervención del soporte. Implementé reglas de elegibilidad, expiración y acumulación de cupones, además de cálculos y validaciones para cancelaciones y cambios. En conjunto, esas automatizaciones redujeron en 34% la necesidad de intervención manual.\n\nOtro desafío fue integrar un monolito PHP 5.7, de más de diez años, al CRM comercial sin poner en riesgo el core de la operación. También evolucioné monitoreo de vuelos, notificaciones y mensajes proactivos.\n\n## Lo que me llevé\n\nAprendí a priorizar intervenciones pequeñas y reversibles cuando el resultado debía llegar rápido. En vez de proponer una transformación amplia, me concentré en puntos que liberaban trabajo operativo.",
      "description": "Mi experiencia en Maxmilhas con automatización posventa e integración de legado.",
      "keywords": [
        "trabajé",
        "cancelaciones",
        "cambios",
        "cupones",
        "para",
        "intervención",
        "contexto",
        "maxmilhas",
        "período",
        "corto"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-maxmilhas",
        "slug": "maxmilhas",
        "title": "Maxmilhas | Rafael Pereira",
        "description": "Mi experiencia en Maxmilhas con automatización posventa e integración de legado.",
        "company": "Maxmilhas",
        "role": "Ingeniero de Software Full Stack",
        "period": "abr/2023 a oct/2023",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/maxmilhas-es.md"
      }
    },
    {
      "id": "5cd8df9bf5ba1779",
      "url": "https://imrafaeldev.site/en/articles/design-patterns-adapter",
      "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern (Part 3)",
      "content": "In the article [Design Patterns: Strategy](/en/articles/design-patterns-strategy/), the focus is swapping algorithms behind a contract. The Adapter isolates external dependencies behind an owned interface. The two complement each other: Strategy varies behavior; Adapter translates the outside world.\n\n---\n\n*Originally published on [LinkedIn](https://www.linkedin.com/pulse/pare-de-ser-ref%C3%A9m-das-depend%C3%AAncias-diga-bem-vindo-ao-design-rafael/) on April 26, 2022.*\n\nAdapter: protocolo e implementações\nCustomerService usa CreateDatabaseCustomerProtocol. MysqlAdapter e PostgresqlAdapter implementam o contrato e traduzem para cada banco.\n\nCamadas: antes e depois\nAcoplamento direto ao MySQL dificulta migração. Com protocolo e adapter, troca-se o plugin sem reescrever o serviço.",
      "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
      "keywords": [
        "adapter",
        "service",
        "business",
        "rule",
        "database",
        "that",
        "interface",
        "mysql",
        "does",
        "plugin"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/en/articles/design-patterns-adapter"
      }
    },
    {
      "id": "5d01b21fa802e4cc",
      "url": "https://imrafaeldev.site/experiences/vbet-pt-br",
      "title": "VBET | Rafael Pereira",
      "content": "## O contexto\n\nNa VBET, trabalhei em um produto de analytics para afiliados e influenciadores de iGaming. O sistema calculava métricas financeiras e operacionais em uma plataforma que passou a atender bases de usuários muito maiores do que as previstas originalmente.\n\n## Como atuei\n\nAntes de atacar desempenho, comecei reduzindo riscos de segurança e manutenção na API legada. Substituí consultas inseguras, organizei a base com Clean Architecture e injeção de dependências e estabeleci testes e documentação para sustentar as próximas mudanças.\n\nDepois, tratei a performance em etapas. Usei Go, goroutines, channels e consultas paralelas para reduzir a primeira etapa do cálculo de comissões de cerca de sete para três minutos. Como o SQL Server era externo e não podia ser alterado, desenhei um ETL com checkpoints, agregações pré-calculadas e reconciliação, distinguindo dados provisórios de dados consolidados.\n\n## O que levo\n\nEssa experiência consolidou a forma como tomo decisões de performance: entender o limite real, aceitar a consistência compatível com cada uso e só então escolher a tecnologia que resolve a restrição.",
      "description": "Minha experiência na VBET com segurança, analytics e performance em escala.",
      "keywords": [
        "para",
        "como",
        "consultas",
        "performance",
        "dados",
        "contexto",
        "vbet",
        "trabalhei",
        "produto",
        "analytics"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-vbet",
        "slug": "vbet",
        "title": "VBET | Rafael Pereira",
        "description": "Minha experiência na VBET com segurança, analytics e performance em escala.",
        "company": "VBET",
        "role": "Engenheiro Backend Sênior",
        "period": "out/2023 a fev/2025",
        "caseSlug": "analytics-sql-server-externo",
        "caseSummary": "O case aprofunda a evolução do dashboard de analytics: da correção de riscos na API à estratégia de ETL, pré-cálculo, reconciliação e cache sobre um SQL Server externo.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/vbet-pt-br.md"
      }
    },
    {
      "id": "5d0a5358ee642509",
      "url": "https://imrafaeldev.site/articles/go-intensive-en",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 5)",
      "content": "**How do you preserve ordering with workers?** Avoid global ordering unless it is required. Partition by a key such as `device_id` and process each partition sequentially.\n\n**How do you handle duplicates?** Use a stable idempotency key, transactional deduplication where possible, and naturally idempotent updates such as an upsert with version or sequence.\n\n**How do you investigate latency?** Separate queue time, processing, and dependencies. Compare p95/p99, lag, and saturation, then test a hypothesis with tracing, `pprof`, or `go tool trace`.\n\n## Production checklist\n\n- Does every goroutine have an owner and a stop condition?\n- Where do context cancellation and deadlines propagate?\n- What is the concurrency limit and what happens at saturation?\n- Is ordering required per key or globally?\n- When does ACK happen, and how does the mutation survive re-delivery?\n- Which failures retry, go to DLQ, or go to quarantine?\n- How do device time and ingestion time differ?\n- Which metrics expose lag, p99, heap, GC, and contention?\n\n## Official references\n\n- [Go 1.26 Release Notes](https://go.dev/doc/go1.26)\n- [The Go Memory Model](https://go.dev/ref/mem)\n- [Go Concurrency Patterns: Context](https://go.dev/blog/context)\n- [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines)\n- [Package errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup)\n- [Data Race Detector](https://go.dev/doc/articles/race_detector)\n- [Diagnostics and profiling](https://go.dev/doc/diagnostics)\n- [Package log/slog](https://pkg.go.dev/log/slog)\n- [Kubernetes probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "context",
        "reading",
        "channel",
        "https",
        "errors",
        "device",
        "with",
        "time",
        "return",
        "goroutine"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-go-intensive",
        "slug": "go-intensive",
        "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes",
        "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
        "excerpt": "A 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "articles/go-intensive-en.md"
      }
    },
    {
      "id": "5d8bd02d6a04465f",
      "url": "https://imrafaeldev.site/experiencias/maxmilhas",
      "title": "Maxmilhas",
      "content": "- [Início](/)\n- Maxmilhas\n\n## Maxmilhas\n\nMinha experiência na Maxmilhas com automação de pós-venda e legado.\n\nCargo\n\nEngenheiro de Software Full Stack\n\nPeríodo\n\nabr/2023 a out/2023\n\n## O contexto\n\nNa Maxmilhas, trabalhei em um período curto e intenso, em fluxos de pós-venda ligados a cancelamentos, remarcações, cupons e comunicação com clientes.\n\n## Como atuei\n\nDesenvolvi microsserviços em Node.js, NestJS e Elixir para automatizar processos que ainda dependiam do suporte. Implementei regras de elegibilidade, expiração e cumulatividade de cupons, além de cálculos e validações necessários aos fluxos de cancelamento e remarcação. O conjunto dessas automações reduziu em 34% a necessidade de intervenção manual.\n\nOutro desafio foi integrar um monólito PHP 5.7, com mais de dez anos, ao CRM comercial sem colocar em risco o core da operação. Também evoluí monitoramento de voos, notificações e mensagens proativas.\n\n## O que levo\n\nAprendi a priorizar intervenções pequenas e reversíveis quando o resultado precisava aparecer rápido. Em vez de propor uma transformação ampla, concentrei a mudança nos pontos que liberavam trabalho operacional.",
      "description": "Minha experiência na Maxmilhas com automação de pós-venda e legado.",
      "keywords": [
        "maxmilhas",
        "2023",
        "pós-venda",
        "período",
        "fluxos",
        "cupons",
        "início",
        "minha",
        "experiência",
        "automação"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/maxmilhas"
      }
    },
    {
      "id": "5de91dd76add78a9",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 3)",
      "content": "Cuidado: usecase que só delega ao protocol sem validar pode estar empurrando regra de negócio para o adapter. Em `CreateUserUsecase`, a checagem de nome duplicado é obrigação do Core.\n\n### Exceptions\n\n`UserAlreadyExistsException` pertence ao Core: fluxo inválido da regra também é regra. Cada falha mapeada a uma exceção conhecida ajuda manutenção. Base com `code` (depois vira status HTTP na borda):\n\n```typescript\n// core/exceptions/IBaseException.ts\nexport abstract class IBaseException extends Error {\n  code: number;\n\n  constructor(message: string) {\n    super(message);\n  }\n}\n```\n\n```typescript\n// core/exceptions/UserAlreadyExistsException.ts\nexport class UserAlreadyExistsException extends IBaseException {\n  constructor(message?: string) {\n    super(message ?? \"User already exists\");\n    this.code = 400;\n  }\n}\n```\n\nO usecase **lança** exceções; **não** as trata. Mapear tipo desconhecido → tipo conhecido fica em adapter ou infra.\n\n### Protocols\n\n`CreateUserProtocol` e `GetUserByNameProtocol` são contratos de acesso a dispositivo externo. Protocol existe para informar ou disparar ação externa — **não** para processar regra de negócio. Preferência: um método público por protocol.\n\n```typescript\n// core/protocols/CreateUserProtocol.ts\nexport abstract class CreateUserProtocol {\n  abstract register(name: string): Promise<UserEntity>;\n}\n```\n\n```typescript\n// core/protocols/GetUserByNameProtocol.ts\nexport abstract class GetUserByNameProtocol {\n  abstract getByName(name: string): Promise<UserEntity | null>;\n}\n```\n\nO Core é o centro; a camada de adaptação liga o resto.\n\n## Adapter\n\nAdapters controlam o tráfego bidirecional: externo → regra e regra → externo. Adaptam objetos, parâmetros e exceções — o mesmo espírito do [padrão Adapter](/artigos/design-patterns-adapter/).\n\nDois grupos:\n\n1. Chamados pelo Core — implementam pelo menos um protocol.\n2. Chamados pela Infra — em geral **services**.\n\n### Connectors, handlers e repositories",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "5dffc26f09a0765c",
      "url": "https://imrafaeldev.site/en",
      "title": "Rafael Pereira, senior software engineer (Part 1)",
      "content": "Rafael Pereira / software engineer / systems\n\n## I work at the points where simple systems *stop being simple*.\n\nCase studies, projects, and technical writing on scale, failure, legacy systems, and business rules. Each piece shows the constraint, the decision, and where the solution stops.\n\n[See the case studies](/en/cases/) [See contact options](/en/contact/)\n\n[Case in focus **Infosistemas** ~98% · fewer intermittent failures in critical flows Open the case](/en/cases/rabbitmq-messaging/) [**VBET** ~7 min → <1 s](/en/cases/external-sql-server-analytics/)\n\n- [~98% Infosistemas: fewer intermittent failures in critical flows](/en/cases/rabbitmq-messaging/)\n- [~7 min → <1 s VBET: commissions, from the original load to the warm cache](/en/cases/external-sql-server-analytics/)\n- [~25% Flapper: fewer tables in the domain-based split](/en/cases/undocumented-monolith-modernization/)\n\nSignal\n\nMethod\n\n## The constraint comes before the diagram.\n\nI start with what happens when a message fails.\n\nThe database cannot change. The legacy core cannot stop.\n\nThe happy path comes later.\n\nThe decision records the discarded alternative and the condition that would justify revisiting it.\n\nCode shows what was built; the case shows why.\n\nProof\n\n## Three systems, three constraints\n\nInfosistemas Feb/2025 – May/2026\n\n~98% fewer intermittent failures in critical flows\n\n### [Infosistemas: a failure contract for RabbitMQ messaging](/en/cases/rabbitmq-messaging/)\n\nIntermittent failures between microservices with no contract for retry, DLQ, or duplication. Adding more consumers only pushed the overload elsewhere.\n\n[Open the case](/en/cases/rabbitmq-messaging/)\n\nContrato de falha na mensageria\nPublicação confirmada, consumo com prefetch controlado, retry com backoff e DLQ por fluxo. Escalar só consumidores fica de fora do desenho.\n\n[Ver o caso](/en/cases/rabbitmq-messaging/)\n\nVBET Oct/2023 – Feb/2025\n\n~7 min → <1 s commissions, from the original load to the warm cache",
      "description": "Institutional portfolio and editorial hub for Rafael Pereira. I work at the points where simple systems stop being simple.",
      "keywords": [
        "cases",
        "projects",
        "case",
        "open",
        "with",
        "systems",
        "stop",
        "decision",
        "contact",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/en"
      }
    },
    {
      "id": "5efb1aaa0c10132d",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-en",
      "title": "Design Patterns: Strategy (Part 1)",
      "content": "`if/else` chains grow and turn fragile. The Strategy pattern encapsulates each algorithm in its own class and lets implementations be swapped at runtime without changing the consuming code.\n\n## The problem: endless if/else\n\nA calculator with addition, subtraction, multiplication, and division usually starts as a `DefaultCalculator` class: private methods per operation and a public function choosing which to invoke with a `switch` or `if/else` chain.\n\n```typescript\nclass DefaultCalculator {\n  public calculate(parameters: BinaryOperationParameters): Result {\n    const { operator, firstOperand, secondOperand } = parameters;\n\n    switch (operator) {\n      case \"*\":\n        return firstOperand * secondOperand;\n      case \"+\":\n        return firstOperand + secondOperand;\n      case \"-\":\n        return firstOperand - secondOperand;\n      case \"/\":\n        return firstOperand / secondOperand;\n      case \"**\":\n        return firstOperand ** secondOperand;\n      case \"%\":\n        return firstOperand % secondOperand;\n      default:\n        throw new Error(\"Operator not found!\");\n    }\n  }\n}\n```\n\nThe problem appears when the calculator must cover more binary operations between integers: percent, exponentiation, modulo, bit shifts. Each new feature changes the original implementation, raises coupling, and makes maintenance more expensive.\n\n## What is the Strategy pattern?\n\nThe pattern defines functionality through a contract (interface), implemented according to context. The interface defines the operation; concrete implementations define its execution.\n\nConsuming code depends on the abstraction. Each strategy stays isolated in its own class. New behaviors arrive without changing existing code, aligned with the Open/Closed Principle.\n\n## Defining the contract\n\nThe first step is the strategy contract interface. For the calculator, something receiving two numbers and returning the result:",
      "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "class",
        "operator",
        "parameters",
        "each",
        "operation"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
        "excerpt": "If/else chains grow and turn fragile. Strategy isolates each algorithm behind a contract.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-en.md"
      }
    },
    {
      "id": "5f47acdcdc6b909b",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-container-with-most-water",
      "title": "Desenoxidando la lógica #02: Container With Most Water (Part 2)",
      "content": "if (paddingLeftArea > paddingRightArea &#x26;&#x26; paddingLeftArea > maxArea) {\nleftIndex += 1;\n} else {\nrigthIndex -= 1;\n}\nEl problema estaba en la hipótesis. La mejor decisión local no garantiza la mejor área en el resto del array. Intentaba adivinar el camino mirando solo los dos próximos movimientos. También había un error en la fórmula de la distancia de ese borrador: para un par de posiciones, el ancho es right - left.\n\n## La observación que destraba el problema\n\nEl área depende de dos cosas: ancho y menor altura.\n\nCuando los punteros están en las posiciones left y right, mover el puntero de la mayor altura no puede aumentar la altura mínima del recipiente. El ancho siempre disminuye, y la altura que limita el área sigue presente.\n\nPor eso, el puntero que debe avanzar es el de la menor altura. Es el único movimiento que puede encontrar una línea más alta y compensar la pérdida de ancho.\n\nSi las alturas son iguales, cualquiera de los dos puede avanzar. En el código, elegí avanzar el de la izquierda cuando heights[leftIndex] &#x3C;= heights[rigthIndex].\n\n## Solución con dos punteros\n\n/**\n* @param {number[]} heights\n* @return {number}\n*/\nvar maxArea = function (heights) {\nlet leftIndex = 0;\nlet rigthIndex = heights.length - 1;\nlet maxArea = 0;\n\nwhile (leftIndex &#x3C; rigthIndex) {\nconst minH = Math.min(heights[leftIndex], heights[rigthIndex]);\nconst currentArea = minH * (rigthIndex - leftIndex);\n\nmaxArea = Math.max(maxArea, currentArea);\n\nif (heights[leftIndex] &#x3C;= heights[rigthIndex]) {\nleftIndex++;\n} else {\nrigthIndex--;\n}\n}\n\nreturn maxArea;\n};\nEn cada ronda, calculo el área del par actual, actualizo la mayor área encontrada y muevo uno de los punteros. El while se aproxima al centro y termina.",
      "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
      "keywords": [
        "área",
        "altura",
        "heights",
        "leftindex",
        "rigthindex",
        "menor",
        "punteros",
        "maxarea",
        "problema",
        "leetcode"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/desenoxidando-logica-container-with-most-water"
      }
    },
    {
      "id": "5f956234d32858ab",
      "url": "https://imrafaeldev.site/en/experience/maxmilhas",
      "title": "Maxmilhas",
      "content": "- [Home](/en/)\n- Maxmilhas\n\n## Maxmilhas\n\nMy experience at Maxmilhas with post-sale automation and legacy integration.\n\nRole\n\nFull Stack Software Engineer\n\nPeriod\n\nApr 2023 to Oct 2023\n\n## Context\n\nAt Maxmilhas, I worked in a short and intense period on travel post-sale flows involving cancellations, rebooking, coupons, and customer communication.\n\n## How I worked\n\nI built Node.js, NestJS, and Elixir microservices to automate processes that still depended on support intervention. I implemented eligibility, expiry, and accumulation rules for coupons, along with calculations and validations for cancellation and rebooking flows. Together, those automations reduced the need for manual intervention by 34%.\n\nAnother challenge was integrating a PHP 5.7 monolith, more than ten years old, with the commercial CRM without putting the operational core at risk. I also evolved flight monitoring, notifications, and proactive customer messages.\n\n## What I took from it\n\nI learned to prioritize small, reversible interventions when outcomes need to arrive quickly. Instead of proposing a broad transformation, I focused on points that released operational work.",
      "description": "My experience at Maxmilhas with post-sale automation and legacy integration.",
      "keywords": [
        "maxmilhas",
        "with",
        "2023",
        "post-sale",
        "period",
        "worked",
        "flows",
        "rebooking",
        "coupons",
        "customer"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/maxmilhas"
      }
    },
    {
      "id": "5fa02b059986177f",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-es",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 3)",
      "content": "Después de eso, el problema principal se volvió la división de los chunks. La estrategia inicial consumía demasiada memoria. En pruebas locales, con datasets menores, el crecimiento proporcional llegó cerca del 15% en algunos momentos.\n\nLa incomodidad venía de una expectativa equivocada: pensé que cambiar a Go resolvería automáticamente el problema. En la práctica, solo había movido el cuello de botella. Antes el límite estaba más claro en CPU. Después, la estrategia de particionamiento empezó a presionar la memoria.\n\nEsto puede pasar por varios motivos: copias innecesarias, buffers grandes, slices manteniendo referencia a arrays mayores, colas internas demasiado grandes o exceso de trabajo preparado antes de procesarse.\n\nEn el resultado final, la memoria aún creció cerca del 5%. En ese caso, la ganancia de tiempo y CPU compensó la pérdida. Pero esto no es una regla universal. Si la carga de producción fuera mucho mayor, o si el servicio corriera con poco margen de memoria, ese intercambio podría dejar de ser aceptable.\n\nEl paralelismo cuesta coordinación, asignación, sincronización y observabilidad. No existe ejecución paralela gratis.\n\n## Cuándo mantendría Node.js\n\nMantendría Node.js sin incomodidad para orquestación de I/O: comunicación por WebSocket, llamadas a varias APIs, consultas en base, disparo de eventos, integración entre servicios y flujos donde el tiempo muerto está en la espera.\n\nEn esos casos, el Event Loop es una excelente elección. Permite alto volumen de operaciones concurrentes sin crear un hilo por request. Para aplicaciones orientadas a eventos, esto es simple, productivo y fácil de encajar en el ecosistema JavaScript.\n\nEl error es intentar empujar ese mismo modelo hacia un cálculo pesado y pensar que la concurrencia de I/O se vuelve paralelismo de CPU. No se vuelve.\n\n## Cuándo miraría hacia Go",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "más",
        "tiempo",
        "mismo",
        "trabajo",
        "loop",
        "problema",
        "para",
        "resultado"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia",
        "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
        "excerpt": "Node.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-es.md"
      }
    },
    {
      "id": "5fff48ea08b61dad",
      "url": "https://imrafaeldev.site/casos/modernizacao-monolito-sem-documentacao",
      "title": "Flapper: descobrir o domínio antes de separar o monólito (Part 1)",
      "content": "- [Início](/)\n- [Casos](/casos/)\n- Flapper: descobrir o domínio antes de separar o monólito\n\nFlapper\n\n## Flapper: descobrir o domínio antes de separar o monólito\n\nO produto não podia parar, e os autores originais já não estavam lá. Antes de migrar, foi preciso descobrir quais fronteiras o banco ainda revelava.\n\nEngenheiro de Software Full Stack set/2021 a jun/2022\n\n~25% menos tabelas na separação por domínios\n\nDescoberta de domínio antes da migração\nO banco legado revela fronteiras; pessoas, autenticação e aeronaves saem gradualmente para contextos com persistência própria.\n\n- [01Contexto](#contexto)\n- [02Restrições](#restricoes)\n- [03Problema](#problema)\n- [04Decisão](#decisao)\n- [05Alternativa descartada](#alternativa-descartada)\n- [06Resultado](#resultado)\n- [07Limitações](#limitacoes)\n\n## Contexto\n\nNa Flapper, o produto principal era um monólito PHP com mais de sete anos, pouca documentação útil e sem os desenvolvedores que o haviam criado. A aplicação sustentava a operação de aviação executiva e não podia ser interrompida para uma reescrita.\n\n## Restrições\n\nO código e o banco acumulavam regras e dependências difíceis de explicar. Alterações tinham efeitos colaterais pouco previsíveis, e não havia especialistas remanescentes para confirmar como cada parte do sistema deveria evoluir. A migração precisava coexistir com o produto em produção.\n\n## Problema\n\nTrocar PHP por outra tecnologia não responderia à dúvida principal: quais regras pertenciam juntas e quais dependências poderiam ser separadas sem quebrar a operação. O sistema precisava de fronteiras de domínio antes de serviços novos.\n\n## Decisão\n\nUsei o banco e o código existente como fonte de descoberta. Agrupamentos de tabelas e relações ajudaram a identificar bounded contexts; a partir deles, a migração seguiu o padrão Strangler:\n\n- extrair um domínio por vez, sem interromper o monólito;\n\n- manter em cada contexto apenas a representação local dos dados de que precisava;",
      "description": "Modernização incremental de um monólito PHP sem documentação na Flapper: banco como fonte de descoberta, bounded contexts e Strangler. A separação por domínios reduziu em aproximadamente 25% o número de tabelas.",
      "keywords": [
        "não",
        "domínio",
        "monólito",
        "banco",
        "para",
        "antes",
        "migração",
        "imrafaeldev",
        "flapper",
        "tabelas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/casos/modernizacao-monolito-sem-documentacao"
      }
    },
    {
      "id": "6053f847e9050da3",
      "url": "https://imrafaeldev.site/es/proyectos/sms-manager",
      "title": "SMS Manager (Part 1)",
      "content": "- [Inicio](/es/)\n- [Proyectos](/es/proyectos/)\n- SMS Manager\n\nRepositorio público\n\n## SMS Manager\n\nImportar CSV no puede bloquear la API Nest. La campaña persiste y publica en la cola RabbitMQ; consumidores en TypeScript, Go o Rust graban el resultado en Mongo. La API no espera a la operadora.\n\n[Repositorio](https://github.com/imrafaeldev/sms-manager)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\n- [01Problema](#problema)\n- [02Restricciones](#restricciones)\n- [03Decisión](#decision)\n- [04Estado actual](#estado-actual)\n- [05Limitaciones](#limitaciones)\n\n## Problema\n\nLas campañas de SMS parten de archivos CSV, usuarios, empresas y autenticación entre servicios. Validar y persistir en el mismo proceso que dispara miles de mensajes acopla la API al ritmo de la operadora y de la cola.\n\n## Restricciones\n\nEl entorno debe ser reproducible. La autenticación entre servicios no puede depender de JWT opaco sin revocación. Los consumidores en más de un lenguaje existen para comparar el mismo contrato de mensajería.\n\n## Decisión\n\nSiete aplicaciones: APIs NestJS de usuarios/autenticación y de empresas/campañas; consumidores equivalentes en Node.js/TypeScript, Go y Rust; aprovisionador declarativo de exchanges, colas y bindings; generador de masa CSV. PostgreSQL/TypeORM para datos relacionales en la API; MongoDB para el resultado del consumo; Redis para caché de token opaco (revocable); gRPC para autenticación entre servicios; RabbitMQ con exchanges topic para los lotes. La API publica y sigue sin bloquearse al ritmo de la operadora.\n\n## Estado actual\n\nRepositorio público con Docker Compose para PostgreSQL, MongoDB, Redis y RabbitMQ. La arquitectura separa dominio, aplicación e infraestructura en los contextos de usuarios, autenticación y empresas; los tres consumidores implementan el mismo contrato de mensaje.\n\n## Limitaciones",
      "description": "Campañas de SMS desacopladas: la API Nest persiste y publica, la cola entrega y consumidores en TypeScript, Go o Rust graban el resultado. Token opaco, Redis y gRPC entre servicios.",
      "keywords": [
        "para",
        "consumidores",
        "operadora",
        "autenticación",
        "contrato",
        "repositorio",
        "rabbitmq",
        "usuarios",
        "empresas",
        "entre"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/es/proyectos/sms-manager"
      }
    },
    {
      "id": "607b4f4715f56cfe",
      "url": "https://imrafaeldev.site/en",
      "title": "Rafael Pereira, senior software engineer (Part 4)",
      "content": "### Limit\n\nThe condition that would justify revisiting the decision.\n\nContact\n\n## Dealing with a system that's stopped being simple?\n\nReach out directly via @imrafaeldev, no form — for professional conversation, start on LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/) [YouTube](https://www.youtube.com/@imrafaeldev) [GitHub](https://github.com/imrafaeldev) [LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Open the contact page](/en/contact/)",
      "description": "Institutional portfolio and editorial hub for Rafael Pereira. I work at the points where simple systems stop being simple.",
      "keywords": [
        "cases",
        "projects",
        "case",
        "open",
        "with",
        "systems",
        "stop",
        "decision",
        "contact",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/en"
      }
    },
    {
      "id": "60d01b8b38f9957a",
      "url": "https://imrafaeldev.site/visual-projects/gabriel-nutricao-es",
      "title": "Gabriel | Nutrición Deportiva",
      "content": "Sitio institucional de Gabriel Pereira sobre nutrición deportiva, con contenido real y estrategia para una rutina de entrenamiento.",
      "description": "Sitio institucional para Gabriel, enfocado en nutrición deportiva para quienes entrenan.",
      "keywords": [
        "sitio",
        "institucional",
        "gabriel",
        "pereira",
        "sobre",
        "nutrición",
        "deportiva",
        "contenido",
        "real",
        "estrategia"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "visual-gabriel-nutricao",
        "slug": "gabriel-nutricion",
        "title": "Gabriel | Nutrición Deportiva",
        "description": "Sitio institucional para Gabriel, enfocado en nutrición deportiva para quienes entrenan.",
        "excerpt": "Sitio institucional de Gabriel Pereira para nutrición deportiva, con contenido real y estrategia para una rutina de entrenamiento.",
        "siteUrl": "https://gabriel-pereira-nutri.vercel.app/",
        "imageUrl": "https://gabriel-pereira-nutri.vercel.app/hero2-desktop.webp",
        "imageAlt": "Gabriel grabando contenido en su estudio en casa, con micrófono, monitor y guitarra al fondo.",
        "imageWidth": "590",
        "imageHeight": "738",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "visual-projects/gabriel-nutricao-es.md"
      }
    },
    {
      "id": "61dba37ef405d205",
      "url": "https://imrafaeldev.site/projetos/sms-manager",
      "title": "SMS Manager (Part 1)",
      "content": "- [Início](/)\n- [Projetos](/projetos/)\n- SMS Manager\n\nRepositório público\n\n## SMS Manager\n\nImportar CSV não pode travar a API Nest. A campanha persiste e publica na fila RabbitMQ; consumidores em TypeScript, Go ou Rust gravam o resultado no Mongo. A API não espera a operadora.\n\n[Repositório](https://github.com/imrafaeldev/sms-manager)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\n- [01Problema](#problema)\n- [02Restrições](#restricoes)\n- [03Decisão](#decisao)\n- [04Estado atual](#estado-atual)\n- [05Limitações](#limitacoes)\n\n## Problema\n\nCampanhas de SMS partem de arquivos CSV, usuários, empresas e autenticação entre serviços. Validar e persistir no mesmo processo que dispara milhares de mensagens acopla a API ao ritmo da operadora e da fila.\n\n## Restrições\n\nO ambiente precisa ser reproduzível. Autenticação entre serviços não pode depender de JWT opaco sem revogação. Consumidores em mais de uma linguagem existem para comparar o mesmo contrato de mensageria.\n\n## Decisão\n\nSete aplicações: APIs NestJS de usuários/autenticação e de empresas/campanhas; consumidores equivalentes em Node.js/TypeScript, Go e Rust; provisionador declarativo de exchanges, filas e bindings; gerador de massa CSV. PostgreSQL/TypeORM para dados relacionais na API; MongoDB para resultado do consumo; Redis para cache de token opaco (revogável); gRPC para autenticação entre serviços; RabbitMQ com exchanges topic para os lotes. A API publica e segue sem bloquear no ritmo da operadora.\n\n## Estado atual\n\nRepositório público com Docker Compose para PostgreSQL, MongoDB, Redis e RabbitMQ. A arquitetura separa domínio, aplicação e infraestrutura nos contextos de usuários, autenticação e empresas; os três consumidores implementam o mesmo contrato de mensagem.\n\n## Limitações",
      "description": "Campanhas de SMS desacopladas: a API Nest persiste e publica, a fila entrega e consumidores em TypeScript, Go ou Rust gravam o resultado. Token opaco, Redis e gRPC entre serviços.",
      "keywords": [
        "para",
        "não",
        "consumidores",
        "operadora",
        "autenticação",
        "mesmo",
        "contrato",
        "repositório",
        "fila",
        "rabbitmq"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/projetos/sms-manager"
      }
    },
    {
      "id": "622f7c05c0aa9d4f",
      "url": "https://imrafaeldev.site/es/proyectos/diffvision",
      "title": "DiffVision",
      "content": "- [Inicio](/es/)\n- [Proyectos](/es/proyectos/)\n- DiffVision\n\nCLI pública; revisión por IA aún mock\n\n## DiffVision\n\nEl diff Git abre en la UI local; comentarios y exportación en Markdown quedan en el repositorio. La revisión visual por IA permanece mock.\n\n[Repositorio](https://github.com/imrafaeldev/diffvision-app)\n\nRevisão local-first\nDiff no disco, UI local, comentários e export em `.diffvision/`. A plataforma remota fica de fora; a revisão por IA visual ainda é mock.\n\n- [01Problema](#problema)\n- [02Restricciones](#restricciones)\n- [03Decisión](#decision)\n- [04Estado actual](#estado-actual)\n- [05Limitaciones](#limitaciones)\n\n## Problema\n\nRevisar un diff Git en herramienta SaaS envía código fuera y mezcla UI remota con el historial local. La revisión necesita funcionar offline, con hunks, filtros, bookmarks y comentarios anclados en líneas.\n\n## Restricciones\n\nPreferencias e informes deben vivir en el propio repositorio (.diffvision/). La CLI npm inicia backend y UI locales. La integración con asistente no puede venderse como lista si aún es prototipo.\n\n## Decisión\n\nLa CLI inspecciona el Git, interpreta diff unificado y sube interfaz web. Backend Fastify con snapshot y WebSocket; UI React/Vite. Exportación Markdown/JSON en el repositorio. Paquete diffvision-mcp por stdio para resumir el repositorio, leer patches y registrar comentarios. El asistente visual de revisión por IA se declara mock/prototipo; la escritura de comentarios vía MCP es funcional.\n\n## Estado actual\n\nDistribuido como CLI npm, con ejecución local-first.\n\n## Limitaciones\n\nNo sustituye el flujo de review de GitHub. El flujo de IA visual no debe leerse como producto acabado.",
      "description": "CLI npm local-first para revisar diffs Git, con UI local, comentarios en el repositorio, exportación en Markdown/JSON y servidor MCP. La revisión visual por IA permanece mock.",
      "keywords": [
        "repositorio",
        "diffvision",
        "revisión",
        "mock",
        "diff",
        "comentarios",
        "visual",
        "local",
        "como",
        "proyectos"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/proyectos/diffvision"
      }
    },
    {
      "id": "62c02776176d5f3d",
      "url": "https://imrafaeldev.site/projects/md2cv-es",
      "title": "md2cv (Part 1)",
      "content": "## Problema\n\nLos perfiles profesionales se esparcen entre docs, LinkedIn y exports. Cada candidatura pide un ángulo distinto. Adaptar el currículo con un LLM sin frontera inventa experiencia y borra el contexto de la versión anterior.\n\nSe necesita un grafo versionado en la máquina que pregunte lo que falta y rechace salida incompleta, sin prometer contratación ni aprobación automática por ATS.\n\n## Restricciones\n\nDesktop y local-first (Electron): sin cuenta propietaria y sin backend dueño de los datos.\n\n- IPC tipado y validado entre renderer y proceso principal.\n- SQLite con foreign keys, WAL, migraciones con checksum, verificaciones de integridad y backup antes de operación destructiva.\n- Agentes (Codex, Cursor, OpenCode) entran por adaptadores aislados, no como dueños de la base.\n- La adaptación a una candidatura no graba en el perfil sin confirmación explícita.\n- Acceso externo solo bajo acción del usuario (búsqueda de URL de empresa o CLI de IA ya configurada en el equipo); el producto no almacena credenciales de proveedores.\n\n## Decisión\n\nRenderer React/Vite separado del proceso principal. El producto organiza el trabajo en un único grafo local:\n\n- Perfil: experiencias, formación, cursos, idiomas, proyectos, enlaces, habilidades y empresas por persona.\n- Currículos: Markdown, versiones inmutables, restauración rastreable y panorama de evolución.\n- ATS: diagnósticos estructurales y puntuación orientativa; PDF textual marcado y DOCX semántico.\n- Candidaturas: empresa, vacante, currículo base, versión y estado en el mismo contexto.\n- Agentes: máquina de estados para preguntas, intentos y propuesta de nueva versión bajo schema; solo persiste con confirmación.\n- Datos: importación y exportación versionada del grafo completo; compilador Markdown (unified/remark) compartido por preview, auditoría y export.",
      "description": "Estudio desktop local-first para perfil profesional, currículos Markdown, versiones inmutables, ATS y adaptación a vacantes con agentes supervisados. Los datos quedan en el SQLite de la máquina.",
      "keywords": [
        "currículo",
        "versión",
        "candidatura",
        "grafo",
        "agentes",
        "base",
        "perfil",
        "bajo",
        "producto",
        "entre"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "projeto-md2cv",
        "slug": "md2cv",
        "title": "md2cv",
        "description": "Estudio desktop local-first para perfil profesional, currículos Markdown, versiones inmutables, ATS y adaptación a vacantes con agentes supervisados. Los datos quedan en el SQLite de la máquina.",
        "excerpt": "Perfil y versiones inmutables quedan en el SQLite de la máquina. El agente supervisado solo propone cambios bajo schema; ATS y exportación en PDF/DOCX reutilizan el mismo grafo, sin backend SaaS dueño de los datos.",
        "repoUrl": "https://github.com/imrafaeldev/md2cv",
        "status": "Producto propio, desktop",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "projects/md2cv-es.md"
      }
    },
    {
      "id": "639a7063494abc4c",
      "url": "https://imrafaeldev.site/experiences/flapper-es",
      "title": "Flapper | Rafael Pereira",
      "content": "## Contexto\n\nEn Flapper, trabajé en una plataforma de aviación ejecutiva cuyo producto principal era un monolito PHP de más de siete años, con poca documentación útil y sin desarrolladores originales disponibles para explicar el sistema.\n\n## Cómo trabajé\n\nEl desafío no era reemplazar PHP por TypeScript. La aplicación estaba en producción y sostenía el negocio, así que empecé por descubrir el dominio. Usé la base de datos para mapear relaciones, identificar bounded contexts y planificar una migración incremental basada en el patrón Strangler.\n\nMigré módulos como personas, autenticación y aeronaves a servicios en Node.js, NestJS y Go. Para reducir dependencias relacionales entre contextos, trabajamos con proyecciones locales y eventos en Kafka; gRPC, REST y GraphQL se usaron según la necesidad de cada integración.\n\n## Lo que me llevé\n\nEsta experiencia consolidó mi visión de modernización de legado: la tecnología viene después de entender fronteras, riesgos y una secuencia que preserve la operación. Además de entregar módulos, documenté decisiones y conduje workshops para que el equipo continuara la transformación.",
      "description": "Mi experiencia en Flapper con modernización incremental de un legado en producción.",
      "keywords": [
        "para",
        "trabajé",
        "módulos",
        "contexto",
        "flapper",
        "plataforma",
        "aviación",
        "ejecutiva",
        "cuyo",
        "producto"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-flapper",
        "slug": "flapper",
        "title": "Flapper | Rafael Pereira",
        "description": "Mi experiencia en Flapper con modernización incremental de un legado en producción.",
        "company": "Flapper",
        "role": "Ingeniero de Software Full Stack",
        "period": "sep/2021 a jun/2022",
        "caseSlug": "modernizacion-monolito-sin-documentacion",
        "caseSummary": "El caso muestra cómo investigué el dominio a partir de la base de datos y el código y conduje la modernización incremental del monolito sin interrumpir la operación.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/flapper-es.md"
      }
    },
    {
      "id": "6458362ac93f3624",
      "url": "https://imrafaeldev.site/casos/analytics-sql-server-externo",
      "title": "VBET: analytics sobre um SQL Server que não podíamos mudar (Part 2)",
      "content": "- paralelizar consultas independentes com Go, goroutines e channels;\n\n- quando o gargalo voltou para o SQL Server, criar ETL e PostgreSQL próprios, com pré-cálculo, checkpoints e reconciliação;\n\n- separar leitura REALTIME (tendência, consistência eventual) de CLOSED (precisão financeira e pagamento);\n\n- cache-aside com TTL alinhado à defasagem aceita de cerca de cinco minutos;\n\n- degradação controlada se o cache falhasse, em vez de derrubar a tela.\n\n## Alternativa descartada\n\nInsistir em índices na base externa como arquitetura, ou recalcular anos de histórico a cada acesso. Também descartada a ideia de pagar o afiliado com o dado REALTIME.\n\n## Resultado\n\nA linha reconciliada no dossiê da experiência, para o caminho de comissão/dashboard, é:\n\n- pior pico inicial: cerca de 7 minutos;\n\n- após queries e índices: cerca de 3 minutos;\n\n- após paralelização: cerca de 1 minuto;\n\n- após ETL/PostgreSQL: cerca de 15 segundos no p99 da comissão sem cache;\n\n- cache quente: menos de 1 segundo.\n\nCada número pertence a essa etapa. Não descreve o ganho de uma decomposição posterior em microsserviços.\n\n## Limitações\n\nA investigação, a decisão de ETL + base própria, a separação REALTIME/CLOSED e a política de degradação são o núcleo atribuível aqui. A decomposição do monólito em Kubernetes é outra história e não mistura o “500%” nem a latência abaixo de 60 ms com este caso. Versões antigas de currículo que citam 30 segundos em carga fria, 11 segundos ou percentuais de SLA sem cenário ficam de fora. Se o produto passar a exigir precisão realtime no pagamento, a separação CLOSED deixa de ser suficiente.\n\nContato\n\n## Tem um sistema que deixou de ser simples?\n\nChame direto pelo @imrafaeldev, sem formulário — para conversa profissional, comece pelo LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir a página de contato](/contato/)",
      "description": "Dashboard de comissões na VBET: SQL Server externo, ETL próprio e cache. A linha medida foi de cerca de sete minutos no pico até menos de um segundo com cache quente.",
      "keywords": [
        "não",
        "cerca",
        "imrafaeldev",
        "vbet",
        "server",
        "dashboard",
        "cache",
        "cada",
        "índices",
        "para"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/casos/analytics-sql-server-externo"
      }
    },
    {
      "id": "6466aa00accc47cd",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 4)",
      "content": "Depois comparo a cardinalidade estimada com a real. Quando o otimizador esperava poucas linhas e recebeu muitas, ele pode ter escolhido joins, memória e agregações para um cenário bem menor do que encontrou durante a execução.\n\nEm seguida acompanho onde os dados crescem. Procuro o operador em que um JOIN leva milhares de linhas a centenas de milhares, pouco antes de um filtro ou `GROUP BY` reduzir tudo novamente. A resposta final pequena pode esconder um volume intermediário enorme. É nesse caminho que agregações e ordenações começam a gastar CPU demais.\n\nTambém não tomo o percentual de custo exibido pelo plano como sentença. Ele serve para escolher onde medir. Marco o operador caro, quantas linhas entram e saem dele e quanto tempo ele consome.\n\nSe o custo aparece depois da multiplicação das linhas e antes dos poucos indicadores necessários, já existe uma hipótese concreta para testar.\n\n## Quebrar a consulta onde o custo cresce\n\nO que resolveu o dashboard foi quebrar a consulta e aproveitar os índices corretamente. A divisão não aconteceu de forma arbitrária. O próprio plano mostrou onde o custo começava a crescer.\n\nMantivemos no banco os filtros e a busca indexada dos registros necessários. Levar esse recorte para a aplicação faria o servidor receber um conjunto maior antes de poder descartá-lo. Também desperdiçaria o caminho que os índices já encurtavam.\n\nA composição final dos indicadores foi para a aplicação. Essa etapa vinha depois dos relacionamentos e das agregações que elevavam o volume intermediário e mantinham alta a CPU do banco. Com os dados já recortados, o servidor podia montar os valores da tela sem concentrar toda a execução em uma única query.\n\nO banco passou a reduzir o conjunto cedo. A aplicação recebia os registros selecionados e compunha os indicadores. Depois da mudança, o dashboard deixou de depender daquela consulta pesada e ficou mais previsível, porque a divisão acompanhava o ponto em que o custo crescia no plano.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "64bee4fd006e8383",
      "url": "https://imrafaeldev.site/cases/flapper-modernizacao-pt-br",
      "title": "Flapper: descobrir o domínio antes de separar o monólito (Part 2)",
      "content": "## Limitações\n\nO número mede a redução de tabelas nessa separação de domínios, não um ganho financeiro, a migração completa ou um resultado de mercado da empresa. A data final da experiência tem divergência histórica em fontes antigas; o período publicado segue o perfil exportado. Se um domínio ainda depender de regras não mapeadas no monólito, sua extração precisa ser adiada ou receber uma integração de transição explícita.",
      "description": "Modernização incremental de um monólito PHP sem documentação na Flapper: banco como fonte de descoberta, bounded contexts e Strangler. A separação por domínios reduziu em aproximadamente 25% o número de tabelas.",
      "keywords": [
        "não",
        "monólito",
        "para",
        "banco",
        "migração",
        "precisava",
        "contexto",
        "regras",
        "domínio",
        "serviços"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "flapper-modernizacao",
        "slug": "modernizacao-monolito-sem-documentacao",
        "title": "Flapper: descobrir o domínio antes de separar o monólito",
        "description": "Modernização incremental de um monólito PHP sem documentação na Flapper: banco como fonte de descoberta, bounded contexts e Strangler. A separação por domínios reduziu em aproximadamente 25% o número de tabelas.",
        "company": "Flapper",
        "role": "Engenheiro de Software Full Stack",
        "period": "set/2021 a jun/2022",
        "excerpt": "O produto não podia parar, e os autores originais já não estavam lá. Antes de migrar, foi preciso descobrir quais fronteiras o banco ainda revelava.",
        "proofValue": "~25%",
        "proofLabel": "menos tabelas na separação por domínios",
        "featuredClaimId": "flapper-domain-modernization",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/flapper-modernizacao-pt-br.md"
      }
    },
    {
      "id": "64d714c291de70e9",
      "url": "https://imrafaeldev.site/projects/md2cv-en",
      "title": "md2cv (Part 1)",
      "content": "## Problem\n\nProfessional profiles scatter across docs, LinkedIn, and exports. Each application asks for a different angle. Adapting a resume with an unbounded LLM invents experience and erases the context of the previous version.\n\nWhat is needed is a versioned graph on the machine that asks what is missing and rejects incomplete output, without promising hiring or automatic ATS approval.\n\n## Constraints\n\nDesktop and local-first (Electron): no proprietary account and no backend owning the data.\n\n- Typed and validated IPC between renderer and main process.\n- SQLite with foreign keys, WAL, checksummed migrations, integrity checks, and backup before destructive operations.\n- Agents (Codex, Cursor, OpenCode) enter through isolated adapters, not as database owners.\n- Job matching never writes to the profile without explicit confirmation.\n- External access only on user action (company URL lookup or an AI CLI already configured on the computer); the product does not store provider credentials.\n\n## Decision\n\nReact/Vite renderer separated from the main process. The product organizes work in a single local graph:\n\n- Profile: experiences, education, courses, languages, projects, links, skills, and companies per person.\n- Resumes: Markdown, immutable versions, traceable restore, and evolution overview.\n- ATS: structural diagnostics and guiding scores; marked textual PDF and semantic DOCX.\n- Applications: company, job post, base resume, version, and state in the same context.\n- Agents: state machine for questions, attempts, and proposed new versions under schema; persists only on confirmation.\n- Data: versioned import and export of the full graph; Markdown compiler (unified/remark) shared by preview, audit, and export.\n\nCanonical flow: profile → base resume → immutable version → ATS audit → PDF/DOCX. The application branch goes through a supervised local agent before the adapted resume.\n\n## Current state",
      "description": "Local-first desktop studio for professional profiles, Markdown resumes, immutable versions, ATS, and job matching with supervised agents. Data stays in the machine's SQLite.",
      "keywords": [
        "resume",
        "with",
        "version",
        "graph",
        "agents",
        "profile",
        "product",
        "from",
        "state",
        "under"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "projeto-md2cv",
        "slug": "md2cv",
        "title": "md2cv",
        "description": "Local-first desktop studio for professional profiles, Markdown resumes, immutable versions, ATS, and job matching with supervised agents. Data stays in the machine's SQLite.",
        "excerpt": "Profile and immutable versions stay in the machine's SQLite. The supervised agent only proposes changes under schema; ATS and PDF/DOCX export reuse the same graph, with no SaaS backend owning the data.",
        "repoUrl": "https://github.com/imrafaeldev/md2cv",
        "status": "Own product, desktop",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "projects/md2cv-en.md"
      }
    },
    {
      "id": "6519e079f635d6af",
      "url": "https://imrafaeldev.site/es/casos/analitica-sql-server-externo",
      "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar (Part 2)",
      "content": "- optimizar queries e índices temporales, como mitigación y no como invariante;\n\n- paralelizar consultas independientes con Go, goroutines y channels;\n\n- cuando el cuello de botella volvió al SQL Server, crear ETL y PostgreSQL propios, con precálculo, checkpoints y reconciliación;\n\n- separar lectura REALTIME (tendencia, consistencia eventual) de CLOSED (precisión financiera y pago);\n\n- cache-aside con TTL alineado al desfase aceptado de cerca de cinco minutos;\n\n- degradación controlada si fallaba la caché, en lugar de tumbar la pantalla.\n\n## Alternativa descartada\n\nInsistir en índices en la base externa como arquitectura, o recalcular años de historial en cada acceso. También se descartó la idea de pagar al afiliado con el dato REALTIME.\n\n## Resultado\n\nLa línea reconciliada en el dosier de la experiencia, para el camino de comisión/dashboard, es:\n\n- peor pico inicial: cerca de 7 minutos;\n\n- tras queries e índices: cerca de 3 minutos;\n\n- tras paralelización: cerca de 1 minuto;\n\n- tras ETL/PostgreSQL: cerca de 15 segundos en el p99 de la comisión sin caché;\n\n- caché caliente: menos de 1 segundo.\n\nCada número pertenece a esa etapa. No describe la ganancia de una descomposición posterior en microservicios.\n\n## Limitaciones\n\nLa investigación, la decisión de ETL + base propia, la separación REALTIME/CLOSED y la política de degradación son el núcleo atribuible aquí. La descomposición del monolito en Kubernetes es otra historia y no mezcla el “500%” ni la latencia por debajo de 60 ms con este caso. Versiones antiguas de currículo que citan 30 segundos en carga fría, 11 segundos o porcentajes de SLA sin escenario quedan fuera. Si el producto pasa a exigir precisión realtime en el pago, la separación CLOSED deja de ser suficiente.\n\nContacto\n\n## ¿Tienes un sistema que dejó de ser simple?\n\nEscríbeme directo por @imrafaeldev, sin formulario — para conversación profesional, empieza en LinkedIn.",
      "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
      "keywords": [
        "cerca",
        "imrafaeldev",
        "vbet",
        "server",
        "base",
        "dashboard",
        "caché",
        "cada",
        "índices",
        "minutos"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/es/casos/analitica-sql-server-externo"
      }
    },
    {
      "id": "6532c7ba83ce44a1",
      "url": "https://imrafaeldev.site/projetos/sms-manager",
      "title": "SMS Manager (Part 2)",
      "content": "É um artefato de estudo e operação local de mensageria, não um produto comercial com SLA de operadora. Os três consumidores demonstram o contrato; não afirmam que as três linguagens rodam juntas em produção do autor.",
      "description": "Campanhas de SMS desacopladas: a API Nest persiste e publica, a fila entrega e consumidores em TypeScript, Go ou Rust gravam o resultado. Token opaco, Redis e gRPC entre serviços.",
      "keywords": [
        "para",
        "não",
        "consumidores",
        "operadora",
        "autenticação",
        "mesmo",
        "contrato",
        "repositório",
        "fila",
        "rabbitmq"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/projetos/sms-manager"
      }
    },
    {
      "id": "655caa4a64c2c09c",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 4)",
      "content": "### Connectors, handlers y repositories\n\nClases que implementan protocols. Cada una adapta **un** dispositivo externo (ORM, cliente HTTP, cola, filesystem).\n\nConvención de nombres:\n\n- **Repositories** — protocol ligado a base (vocabulario familiar).\n- **Connectors** — retornan datos sin ser \"tabla\" (ej.: `ClientHttpFetchConnector`, `ClientHttpAxiosConnector`).\n- **Handlers** — procesan sin retorno síncrono (ej.: publicar en Kafka).\n\nOtros nombres son válidos; el criterio es un adapter por dispositivo.\n\nEn el CRUD, solo repository (mock):\n\n```typescript\n// adapters/repositories/UsersMockRepository.ts\nexport class UsersMockRepository\n  implements\n    GetUserByIdProtocol,\n    GetUserByNameProtocol,\n    CreateUserProtocol,\n    UpdateUserProtocol,\n    DeleteUserProtocol\n{\n  private db: DbConnector;\n\n  constructor() {\n    this.db = mockDbConnector;\n  }\n\n  async getById(id: string): Promise<UserEntity> {\n    return this.db.users.getById(id);\n  }\n\n  async getByName(name: string): Promise<UserEntity | null> {\n    return this.db.users.getByName(name);\n  }\n\n  async register(name: string): Promise<UserEntity> {\n    return this.db.users.register(name);\n  }\n\n  async update(id: string, name: string): Promise<UserEntity> {\n    return this.db.users.update(id, name);\n  }\n\n  async delete(id: string): Promise<void> {\n    return this.db.users.delete(id);\n  }\n}\n```\n\nMock del conector:",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "66fc662870087b32",
      "url": "https://imrafaeldev.site/es/casos/mensajeria-rabbitmq",
      "title": "Infosistemas: contrato de fallo en la mensajería RabbitMQ (Part 2)",
      "content": "- DLQ por flujo, para el mensaje que no debe desaparecer ni repetirse sin control;\n\n- retry con backoff para indisponibilidad temporal;\n\n- idempotencia y deduplicación en el consumidor, porque la entrega duplicada no puede repetir el efecto de negocio;\n\n- publisher confirms, para reducir la incertidumbre en la publicación;\n\n- ajuste de prefetch, en lugar de abrir concurrencia indiscriminada.\n\n## Alternativa descartada\n\nTratar el problema como falta de capacidad (más consumidores, más prefetch) sin cambiar el contrato de fallo. Eso movería el cuello de botella y mantendría pérdida o duplicidad silenciosa.\n\n## Implementación\n\nEl rediseño colocó estos mecanismos en los flujos críticos: publicación confirmada, cola durable con prefetch controlado, consumidor idempotente, retry con backoff y DLQ por flujo. La operación pasó a tener un camino predecible para el fallo temporal y para el fallo permanente, en lugar de depender de reprocesamiento ad hoc.\n\n## Resultado\n\nLa reducción registrada en los fallos intermitentes de los flujos críticos entre microservicios fue de aproximadamente el 98%. El número describe esos flujos tras el rediseño, no la operación entera de la empresa ni otros frentes.\n\n## Limitaciones\n\nLas métricas de otros frentes aún pendientes de método o confirmación quedan fuera. Si la volumetría o el mapa de microservicios cambia de forma que DLQ y prefetch dejen de aislar el fallo, el tuning debe revisarse con telemetría de colas y de consumidores.\n\nContacto\n\n## ¿Tienes un sistema que dejó de ser simple?\n\nEscríbeme directo por @imrafaeldev, sin formulario — para conversación profesional, empieza en LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir la página de contacto](/es/contacto/)",
      "description": "Rediseño de la mensajería RabbitMQ en Infosistemas con colas durables, DLQ, retry, idempotencia y prefetch. El trabajo redujo en cerca del 98% los fallos intermitentes entre microservicios.",
      "keywords": [
        "fallo",
        "para",
        "contrato",
        "prefetch",
        "consumidores",
        "flujos",
        "imrafaeldev",
        "infosistemas",
        "mensajería",
        "fallos"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/es/casos/mensajeria-rabbitmq"
      }
    },
    {
      "id": "6757afdabdfc7f5d",
      "url": "https://imrafaeldev.site/experiences/braistech-es",
      "title": "Braistech | Rafael Pereira",
      "content": "## Contexto\n\nEn Braistech, tuve una de mis primeras experiencias de producto en un entorno pequeño, con pocas personas y responsabilidad distribuida. El dominio involucraba contratos de criptoactivos y movimientos financieros.\n\n## Cómo trabajé\n\nLideré la estructuración del sistema principal con Node.js y NestJS, participé en el diseño de microservicios para el núcleo del negocio y desarrollé aplicaciones Flutter. También construí un sistema de contratos y trabajé en integraciones de pago relacionadas con el ecosistema de Binance.\n\nParticipé además en la transición de una organización MVC hacia Clean Architecture. El objetivo era reducir acoplamiento y facilitar el mantenimiento de un sistema en crecimiento, mientras orientaba a desarrolladores junior en las decisiones de código.\n\n## Lo que me llevé\n\nEsta etapa consolidó mi interés por backend y arquitectura. Trabajar en todo el producto también me dio una visión full stack que sigue siendo útil en conversaciones con frontend y producto.",
      "description": "Mi experiencia en Braistech con producto, microservicios y criptoactivos.",
      "keywords": [
        "producto",
        "sistema",
        "contratos",
        "trabajé",
        "participé",
        "también",
        "contexto",
        "braistech",
        "tuve",
        "primeras"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-braistech",
        "slug": "braistech",
        "title": "Braistech | Rafael Pereira",
        "description": "Mi experiencia en Braistech con producto, microservicios y criptoactivos.",
        "company": "Braistech",
        "role": "Ingeniero de Software Full Stack Pleno",
        "period": "nov/2019 a ene/2021",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/braistech-es.md"
      }
    },
    {
      "id": "67fc56b12faeded4",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 5)",
      "content": "```typescript\nexport const mockDbConnector: DbConnector = {\n  users: {\n    getById: async (id: string) =>\n      Promise.resolve(new UserEntity({ id, name: \"Test\" })),\n    getByName: async (name: string) =>\n      Promise.resolve(new UserEntity({ id: \"1\", name })),\n    register: async (name: string) =>\n      Promise.resolve(new UserEntity({ id: \"2\", name })),\n    update: async (id: string, name: string) =>\n      Promise.resolve(new UserEntity({ id, name })),\n    delete: async (_id: string) => Promise.resolve(),\n  },\n  profiles: {\n    getById: async (_id: string) => Promise.resolve(null),\n    getByName: async (_name: string) => Promise.resolve(null),\n    register: async (_name: string) => Promise.resolve(null),\n    update: async (_id: string, _name: string) => Promise.resolve(null),\n    delete: async (_id: string) => Promise.resolve(),\n  },\n};\n```\n\n`UserEntity` (Core) no es la tabla de la base. Son cosas distintas.\n\n**\"¿Implementar varios protocols hiere la S de SOLID?\"** Depende. Separar cada protocol en clase propia maximiza SRP. Mantener un repository por entidad/agregado también es coherente: el alcance es \"datos de User en este conector\". Criterios prácticos para *no* juntar A y B en la misma clase:\n\n1. Implementar B exigiría otra dependencia externa.\n2. A y B trabajan entidades/alcances distintos.\n3. La complejidad ciclomática (o cognitiva) sube demasiado.\n4. La clase pasa de ~500 líneas — umbral subjetivo; usa criterio de equipo.\n\nCon DI y segregación de interfaz, `CreateUserUsecase` solo conoce `CreateUserProtocol` y `GetUserByNameProtocol`. En runtime puede recibir la misma instancia de `UsersMockRepository` en los dos parámetros — sin acoplarse a la clase concreta.\n\n### Services\n\nLos services adaptan llamadas de la Infra hacia el Core: REST → service → usecase → protocol → repository → base. El mismo diseño vale para handler de cola o resolver GraphQL: entrada distinta, service en el medio.",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "6824d5745faef154",
      "url": "https://imrafaeldev.site/artigos/desenferrujando-logica-container-with-most-water",
      "title": "Desenferrujando a lógica #02: Container With Most Water (Part 2)",
      "content": "if (paddingLeftArea > paddingRightArea &#x26;&#x26; paddingLeftArea > maxArea) {\nleftIndex += 1;\n} else {\nrigthIndex -= 1;\n}\nO problema estava na hipótese. A melhor decisão local não garante a melhor área no restante do array. Eu tentava adivinhar o caminho olhando apenas para os dois próximos movimentos. Também havia um erro na fórmula da distância desse rascunho: para um par de posições, a largura é right - left.\n\n## A observação que destrava o problema\n\nA área depende de duas coisas: largura e menor altura.\n\nQuando os ponteiros estão nas posições left e right, mover o ponteiro da maior altura não pode aumentar a altura mínima do recipiente. A largura sempre diminui, e a altura que limita a área continua presente.\n\nPor isso, o ponteiro que deve avançar é o da menor altura. É o único movimento que pode encontrar uma linha mais alta e compensar a perda de largura.\n\nSe as alturas forem iguais, qualquer um dos dois pode avançar. No código, escolhi avançar o da esquerda quando heights[leftIndex] &#x3C;= heights[rigthIndex].\n\n## Solução com dois ponteiros\n\n/**\n* @param {number[]} heights\n* @return {number}\n*/\nvar maxArea = function (heights) {\nlet leftIndex = 0;\nlet rigthIndex = heights.length - 1;\nlet maxArea = 0;\n\nwhile (leftIndex &#x3C; rigthIndex) {\nconst minH = Math.min(heights[leftIndex], heights[rigthIndex]);\nconst currentArea = minH * (rigthIndex - leftIndex);\n\nmaxArea = Math.max(maxArea, currentArea);\n\nif (heights[leftIndex] &#x3C;= heights[rigthIndex]) {\nleftIndex++;\n} else {\nrigthIndex--;\n}\n}\n\nreturn maxArea;\n};\nA cada rodada, calculo a área do par atual, atualizo a maior área encontrada e movo um dos ponteiros. O while se aproxima do centro e termina.",
      "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
      "keywords": [
        "área",
        "altura",
        "heights",
        "leftindex",
        "rigthindex",
        "largura",
        "menor",
        "ponteiros",
        "para",
        "maior"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/artigos/desenferrujando-logica-container-with-most-water"
      }
    },
    {
      "id": "68400ac015623824",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-es",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 2)",
      "content": "### Entities\n\n```typescript\n// core/entities/UserEntity.ts\nexport interface UserEntityProps {\n  id?: string;\n  name: string;\n}\n\nexport class UserEntity {\n  constructor(private readonly props: UserEntityProps) {}\n\n  get id(): string {\n    return this.props.id ?? \"\";\n  }\n\n  get name(): string {\n    return this.props.name;\n  }\n}\n```\n\nLa entidad recibe props tipadas y expone getters. Depende de una interfaz que cualquier DTO de transferencia puede satisfacer después.\n\n### Features y usecases\n\nEl CRUD necesita crear, buscar, actualizar y eliminar. En el Core, cada usecase implementa un contrato (feature) con un único método público — alineado a Liskov, abierto/cerrado, segregación de interfaz y responsabilidad única. El usecase **no** accede a la base: conoce **protocols** que describen la acción externa (inversión de dependencia).\n\nRegistro: nombre obligatorio; si ya existe, error; si no, retorna `UserEntity`.\n\n- contrato `CreateUser`\n- implementación `CreateUserUsecase`\n\nEn TypeScript, clase abstracta con métodos abstractos funciona como contrato *y* valor — útil para DI (`const createUserSymbol = CreateUser`):\n\n```typescript\n// core/features/CreateUser.ts\nexport abstract class CreateUser {\n  abstract execute(name: string): Promise<UserEntity>;\n}\n```\n\n```typescript\n// core/usecases/CreateUserUsecase.ts\nexport class CreateUserUsecase implements CreateUser {\n  constructor(\n    private readonly createUserProtocol: CreateUserProtocol,\n    private readonly getByNameProtocol: GetUserByNameProtocol,\n  ) {}\n\n  async execute(name: string): Promise<UserEntity> {\n    const existsName = await this.getByNameProtocol.getByName(name);\n\n    if (existsName) {\n      throw new UserAlreadyExistsException(\n        `the name ${name} already exists`,\n      );\n    }\n\n    return this.createUserProtocol.register(name);\n  }\n}\n```\n\nEl usecase define *qué* (validar nombre, registrar). No define *cómo* buscar o persistir. La regla queda independiente de lib, framework y base.",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "this",
        "export",
        "class",
        "return",
        "private",
        "service",
        "readonly"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
        "excerpt": "Arquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-es.md"
      }
    },
    {
      "id": "685bda0580e7b7b9",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 6)",
      "content": "```typescript\nexport class UserService {\n  constructor(\n    private createUserUsecase: CreateUser,\n    private updateUserUsecase: UpdateUser,\n    private deleteUserUsecase: DeleteUser,\n    private getUserUsecase: GetUser,\n  ) {}\n\n  async getUser(id: string): Promise<UserEntity> {\n    try {\n      return await this.getUserUsecase.execute(id);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async createUser(name: string): Promise<UserEntity> {\n    try {\n      return await this.createUserUsecase.execute(name);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async updateUser(id: string, name: string): Promise<UserEntity> {\n    try {\n      return await this.updateUserUsecase.execute(id, name);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async deleteUser(id: string): Promise<void> {\n    try {\n      return await this.deleteUserUsecase.execute(id);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n}\n```\n\nO service depende das **features** (contratos), não das classes concretas de usecase. Mapeia exceção de Core para `RestError` que a Infra traduz em resposta HTTP.\n\nBoa prática: services só para Infra; um service por “instrumento” de entrada (controller HTTP ≠ handler de fila), salvo middleware do mesmo stack que reutiliza o mesmo service.\n\n## Infra\n\nFramework, DI, controllers, DTOs e boilerplate que não é negócio nem adapter.\n\nController NestJS:\n\n```typescript\n// infra/controllers/UserController.ts\n@Controller(\"users\")\nexport class UserController {\n  constructor(private service: UserService) {}\n\n  @Get(\":id\")\n  async getUser(@Param(\"id\") id: string): Promise<UserEntity> {\n    return this.service.getUser(id);\n  }",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "688c74fde35dd401",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 4)",
      "content": "Escolha a primitiva pela propriedade que precisa preservar:\n\n| Necessidade | Primitiva |\n| --- | --- |\n| Contador ou flag independente | `atomic` tipado |\n| Invariante entre vários campos | `sync.Mutex` |\n| Leitura frequente e escrita curta | `sync.RWMutex`, depois de medir |\n| Transferir trabalho ou ownership | channel |\n| Inicialização única | `sync.Once` |\n| Esperar tarefas sem erro | `sync.WaitGroup` |\n| Esperar tarefas com erro e cancelamento | `errgroup` |\n\nNão copie mutex depois do primeiro uso e não mantenha lock durante I/O remoto. `RWMutex` não é uma melhoria automática para uma seção crítica pequena.\n\nBackpressure é uma decisão de produto e operação. Se a ingestão recebe 50 mil mensagens por segundo e a persistência confirma 20 mil, acumular o restante em memória apenas muda o incidente de lugar.\n\n| Política | Consequência |\n| --- | --- |\n| Bloquear produtor | Aumenta latência e preserva dados quando o protocolo aceita desacelerar |\n| Rejeitar com erro | Exige retry e idempotência no cliente |\n| Pausar consumo ou ACK | Mantém backlog no broker durável |\n| Descartar dados antigos | Preserva frescor quando histórico não importa |\n| Agregar ou downsample | Reduz resolução para aliviar a carga |\n| Persistir em disco | Evita perda, com custo operacional adicional |\n\nDefina tamanho de buffer, métrica de ocupação, timeout e ação de saturação. Buffer sem política não é estratégia de capacidade.\n\n## Pipeline IoT que tolera reentrega\n\nUma separação comum é:\n\n```text\ndispositivo -> MQTT/broker -> ingestão Go -> stream -> processadores -> armazenamento\n                                  \\-> DLQ        \\-> estado atual\n```\n\nMQTT atende bem à borda e às conexões dos dispositivos. Um stream como Kafka atende retenção, replay e particionamento interno. gRPC é RPC interno tipado; WebSocket atende atualização de dashboards. Nenhum deles substitui os outros automaticamente.",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "68cc01d1c38c0f88",
      "url": "https://imrafaeldev.site/projects/goc-mcp-pt-br",
      "title": "goc_mcp",
      "content": "## Problema\n\nUm maestro (Codex ou Cursor) precisa delegar trabalho a workers OpenCode com ciclo de vida explícito: planejar, iniciar, acompanhar, responder, cancelar e recuperar, sem recursão infinita de agentes.\n\n## Restrições\n\nTudo é local. O daemon autentica em loopback. Há limite global e por workspace. Tarefas interrompidas precisam ser reconciliadas. Corrupção de estado não pode virar escrita cega. No caminho feliz: um worker OpenCode por vez, sem decomposição automática que deixe o maestro sem controle.\n\n## Decisão\n\nImplementação em Go: gateways MCP por stdio, daemon único, máquina de estados e executor FIFO. Persistência em SQLite com WAL; artefatos em JSONL. Adaptador OpenCode com servidor como caminho principal e CLI como fallback. Isolamento contra delegação recursiva e diagnóstico somente leitura se o estado corromper.\n\n## Estado atual\n\nRepositório com testes, ADRs e benchmarks. A orquestração funcionou. As medições publicadas no próprio projeto não confirmaram a hipótese de reduzir tempo e custo.\n\n## Limitações\n\nÉ um experimento. Não afirma ganho de produtividade de engenharia de agentes em produção. O resultado negativo da hipótese faz parte do artefato.",
      "description": "Orquestração local de agentes via MCP em Go: maestro, daemon FIFO, workers OpenCode e SQLite. A entrega funcionou, mas a hipótese de custo e tempo não foi confirmada nesta medição.",
      "keywords": [
        "opencode",
        "estado",
        "não",
        "maestro",
        "agentes",
        "daemon",
        "caminho",
        "como",
        "hipótese",
        "problema"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "projeto-goc-mcp",
        "slug": "goc-mcp",
        "title": "goc_mcp",
        "description": "Orquestração local de agentes via MCP em Go: maestro, daemon FIFO, workers OpenCode e SQLite. A entrega funcionou, mas a hipótese de custo e tempo não foi confirmada nesta medição.",
        "excerpt": "Maestro (Codex/Cursor) delega via MCP; daemon FIFO e workers OpenCode executam com estado em SQLite. A orquestração funcionou, mas a medição não confirmou redução de custo ou tempo.",
        "repoUrl": "https://github.com/imrafaeldev/goc_mcp",
        "status": "Experimento documentado",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/goc-mcp-pt-br.md"
      }
    },
    {
      "id": "68edc36fc20867a3",
      "url": "https://imrafaeldev.site/cases/flapper-modernizacao-es",
      "title": "Flapper: descubrir el dominio antes de separar el monolito (Part 2)",
      "content": "La separación por dominios redujo en aproximadamente el 25% el número de tablas y creó siete bases organizadas por contexto. Los módulos de personas, autenticación y aeronaves fueron los primeros pasos de una transformación planeada para continuar más allá de la entrega inicial.\n\n## Limitaciones\n\nEl número mide la reducción de tablas en esa separación de dominios, no una ganancia financiera, la migración completa o un resultado de mercado de la empresa. La fecha final de la experiencia tiene divergencia histórica en fuentes antiguas; el período publicado sigue el perfil exportado. Si un dominio aún depende de reglas no mapeadas en el monolito, su extracción debe posponerse o recibir una integración de transición explícita.",
      "description": "Modernización incremental de un monolito PHP sin documentación en Flapper: base como fuente de descubrimiento, bounded contexts y Strangler. La separación por dominios redujo en aproximadamente el 25% el número de tablas.",
      "keywords": [
        "monolito",
        "para",
        "base",
        "migración",
        "necesitaba",
        "contexto",
        "reglas",
        "dominio",
        "servicios",
        "tablas"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "flapper-modernizacao",
        "slug": "modernizacion-monolito-sin-documentacion",
        "title": "Flapper: descubrir el dominio antes de separar el monolito",
        "description": "Modernización incremental de un monolito PHP sin documentación en Flapper: base como fuente de descubrimiento, bounded contexts y Strangler. La separación por dominios redujo en aproximadamente el 25% el número de tablas.",
        "company": "Flapper",
        "role": "Ingeniero de Software Full Stack",
        "period": "sep/2021 – jun/2022",
        "excerpt": "El producto no podía parar, y los autores originales ya no estaban. Antes de migrar, fue preciso descubrir qué fronteras aún revelaba la base.",
        "proofValue": "~25%",
        "proofLabel": "menos tablas en la separación por dominios",
        "featuredClaimId": "flapper-domain-modernization",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/flapper-modernizacao-es.md"
      }
    },
    {
      "id": "6bf2a17277e70b08",
      "url": "https://imrafaeldev.site/artigos/backend-performance-perto-dos-dados",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 4)",
      "content": "Depois de comparar as versões do filtro, leio o plano como uma história do trabalho produzido pela consulta. Começo pelo tempo total e pelas leituras. Se a tela devolve dez indicadores, mas a execução faz centenas de milhares de leituras, existe uma conta para explicar.\n\nDepois comparo a cardinalidade estimada com a real. Quando o otimizador esperava poucas linhas e recebeu muitas, ele pode ter escolhido joins, memória e agregações para um cenário bem menor do que encontrou durante a execução.\n\nEm seguida acompanho onde os dados crescem. Procuro o operador em que um JOIN leva milhares de linhas a centenas de milhares, pouco antes de um filtro ou GROUP BY reduzir tudo novamente. A resposta final pequena pode esconder um volume intermediário enorme. É nesse caminho que agregações e ordenações começam a gastar CPU demais.\n\nTambém não tomo o percentual de custo exibido pelo plano como sentença. Ele serve para escolher onde medir. Marco o operador caro, quantas linhas entram e saem dele e quanto tempo ele consome.\n\nSe o custo aparece depois da multiplicação das linhas e antes dos poucos indicadores necessários, já existe uma hipótese concreta para testar.\n\n## Quebrar a consulta onde o custo cresce\n\nO que resolveu o dashboard foi quebrar a consulta e aproveitar os índices corretamente. A divisão não aconteceu de forma arbitrária. O próprio plano mostrou onde o custo começava a crescer.\n\nMantivemos no banco os filtros e a busca indexada dos registros necessários. Levar esse recorte para a aplicação faria o servidor receber um conjunto maior antes de poder descartá-lo. Também desperdiçaria o caminho que os índices já encurtavam.\n\nA composição final dos indicadores foi para a aplicação. Essa etapa vinha depois dos relacionamentos e das agregações que elevavam o volume intermediário e mantinham alta a CPU do banco. Com os dados já recortados, o servidor podia montar os valores da tela sem concentrar toda a execução em uma única query.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "antes",
        "execução",
        "dados",
        "não",
        "trabalho"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/artigos/backend-performance-perto-dos-dados"
      }
    },
    {
      "id": "6bf3dd36ab59cb1a",
      "url": "https://imrafaeldev.site/cases/vbet-analytics-es",
      "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar (Part 2)",
      "content": "La línea reconciliada en el dosier de la experiencia, para el camino de comisión/dashboard, es:\n\n- peor pico inicial: cerca de 7 minutos;\n- tras queries e índices: cerca de 3 minutos;\n- tras paralelización: cerca de 1 minuto;\n- tras ETL/PostgreSQL: cerca de 15 segundos en el p99 de la comisión sin caché;\n- caché caliente: menos de 1 segundo.\n\nCada número pertenece a esa etapa. No describe la ganancia de una descomposición posterior en microservicios.\n\n## Limitaciones\n\nLa investigación, la decisión de ETL + base propia, la separación REALTIME/CLOSED y la política de degradación son el núcleo atribuible aquí. La descomposición del monolito en Kubernetes es otra historia y no mezcla el \"500%\" ni la latencia por debajo de 60 ms con este caso. Versiones antiguas de currículo que citan 30 segundos en carga fría, 11 segundos o porcentajes de SLA sin escenario quedan fuera. Si el producto pasa a exigir precisión realtime en el pago, la separación CLOSED deja de ser suficiente.",
      "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
      "keywords": [
        "cerca",
        "índices",
        "minutos",
        "realtime",
        "influencers",
        "dashboard",
        "comisión",
        "pago",
        "base",
        "queries"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "vbet-analytics",
        "slug": "analitica-sql-server-externo",
        "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar",
        "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
        "company": "VBET",
        "role": "Ingeniero Backend Sénior",
        "period": "oct/2023 – feb/2025",
        "excerpt": "La base era de otro equipo. El dashboard necesitaba dejar de depender de un schema que no controlábamos.",
        "proofValue": "~7 min → <1 s",
        "proofLabel": "comisiones, de la carga original a la caché caliente",
        "featuredClaimId": "vbet-commission-performance",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/vbet-analytics-es.md"
      }
    },
    {
      "id": "6c15a3c8ebfbd851",
      "url": "https://imrafaeldev.site/en/articles/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 4)",
      "content": "Adapters control bidirectional traffic: external → rule and rule → external. They adapt objects, parameters, and exceptions — the same spirit as the [Adapter pattern](/en/articles/design-patterns-adapter/).\n\nTwo groups:\n\n- Called by Core — implement at least one protocol.\n\n- Called by Infra — generally **services**.\n\n### Connectors, handlers, and repositories\n\nClasses implementing protocols. Each adapts **one** external device (ORM, HTTP client, queue, filesystem).\n\nNaming convention:\n\n- **Repositories** — protocol tied to a database (familiar vocabulary).\n\n- **Connectors** — return data without being a “table” (e.g. ClientHttpFetchConnector, ClientHttpAxiosConnector).\n\n- **Handlers** — process without synchronous return (e.g. publishing to Kafka).\n\nOther names are valid; the criterion is one adapter per device.\n\nIn the CRUD, only repository (mock):\n\n// adapters/repositories/UsersMockRepository.ts\nexport class UsersMockRepository\nimplements\nGetUserByIdProtocol,\nGetUserByNameProtocol,\nCreateUserProtocol,\nUpdateUserProtocol,\nDeleteUserProtocol\n{\nprivate db: DbConnector;\n\nconstructor() {\nthis.db = mockDbConnector;\n}\n\nasync getById(id: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.getById(id);\n}\n\nasync getByName(name: string): Promise&#x3C;UserEntity | null> {\nreturn this.db.users.getByName(name);\n}\n\nasync register(name: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.register(name);\n}\n\nasync update(id: string, name: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.update(id, name);\n}\n\nasync delete(id: string): Promise&#x3C;void> {\nreturn this.db.users.delete(id);\n}\n}\nMock connector:",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "architecture",
        "return",
        "export",
        "class",
        "adapters"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/en/articles/typescript-clean-architecture"
      }
    },
    {
      "id": "6e9828fd98309b0e",
      "url": "https://imrafaeldev.site/en/articles/backend-performance-close-to-data",
      "title": "Most backend performance problems start close to the data (Part 4)",
      "content": "Next I follow where data grows. I look for the operator where a JOIN takes thousands of rows to hundreds of thousands, just before a filter or GROUP BY shrinks everything again. The small final answer may hide an enormous intermediate volume. That is where aggregations and sorts start spending too much CPU.\n\nI also do not take the plan’s displayed cost percentage as verdict. It serves to choose where to measure. I mark the expensive operator, how many rows enter and leave it, and how much time it consumes.\n\nIf cost shows up after row multiplication and before the few needed indicators, there is already a concrete hypothesis to test.\n\n## Split the query where cost grows\n\nWhat fixed the dashboard was splitting the query and using indexes properly. The split did not happen arbitrarily. The plan itself showed where cost started growing.\n\nWe kept in the database the filters and indexed lookup of the needed records. Moving that slice to the application would make the server receive a larger set before it could discard it. It would also waste the path indexes already shortened.\n\nFinal indicator composition moved to the application. That stage came after the relationships and aggregations that raised intermediate volume and kept database CPU high. With data already sliced, the server could assemble screen values without concentrating all execution in a single query.\n\nThe database started reducing the set early. The application received the selected records and composed the indicators. After the change, the dashboard stopped depending on that heavy query and became more predictable, because the split followed the point where cost grew in the plan.\n\nThat boundary did not come from a preference for single queries or for more application logic. I marked where CPU, reads, and intermediate rows spiked after selective filters. Then I checked whether the expensive stretch could receive an already-reduced set outside the database.",
      "description": "Before adding machines, cache, or queues, measure the request",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "before",
        "with",
        "where",
        "start",
        "column",
        "data"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/en/articles/backend-performance-close-to-data"
      }
    },
    {
      "id": "6f6623dbc2400279",
      "url": "https://imrafaeldev.site/experiences/south-system-quiq-itau-es",
      "title": "South System, QUIQ e Itaú | Rafael Pereira",
      "content": "## Contexto\n\nEn South System, fui asignado a QUIQ para trabajar en un Marketplace as a Service para instituciones financieras. Itaú fue el primer contexto, pero el producto necesitaba incorporar nuevos bancos sin requerir un fork por cliente.\n\n## Cómo trabajé\n\nParticipé en arquitectura, modelado de datos, elección de tecnologías y refinamiento de reglas con producto. El resultado fue una plataforma white-label y multi-tenant, con aislamiento lógico entre tenants y Arquitectura Hexagonal para separar el dominio de las integraciones específicas.\n\nTrabajé con Node.js, TypeScript, MySQL, servicios asíncronos en Go y AWS. Estructuré pruebas unitarias y de integración para casos críticos y usé análisis estático como parte del flujo de calidad.\n\n## Lo que me llevé\n\nEsta experiencia cambió cómo comunico arquitectura. Pasé a tratar la alineación con producto, Product Owner y stakeholders como parte de la decisión técnica, no como una etapa posterior al código.",
      "description": "Mi experiencia con un marketplace white-label y multi-tenant para instituciones financieras.",
      "keywords": [
        "para",
        "producto",
        "arquitectura",
        "como",
        "contexto",
        "cómo",
        "trabajé",
        "parte",
        "south",
        "system"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-south-system-quiq-itau",
        "slug": "south-system-quiq-itau",
        "title": "South System, QUIQ e Itaú | Rafael Pereira",
        "description": "Mi experiencia con un marketplace white-label y multi-tenant para instituciones financieras.",
        "company": "South System (asignado a QUIQ/Itaú)",
        "role": "Ingeniero Backend",
        "period": "jun/2022 a abr/2023",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/south-system-quiq-itau-es.md"
      }
    },
    {
      "id": "700a3ec1e2edeaa7",
      "url": "https://imrafaeldev.site/es/articulos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 4)",
      "content": "En esos casos, el Event Loop es una excelente elección. Permite alto volumen de operaciones concurrentes sin crear un hilo por request. Para aplicaciones orientadas a eventos, esto es simple, productivo y fácil de encajar en el ecosistema JavaScript.\n\nEl error es intentar empujar ese mismo modelo hacia un cálculo pesado y pensar que la concurrencia de I/O se vuelve paralelismo de CPU. No se vuelve.\n\n## Cuándo miraría hacia Go\n\nEmpezaría a mirar hacia Go cuando la tarea fuera claramente CPU bound: cálculo en alto volumen de datos, procesamiento de imagen en lote, agregaciones pesadas, compresión, transformación grande de buffers, simulaciones o cualquier rutina en que la máquina pase más tiempo calculando que esperando respuesta externa.\n\nEn ese tipo de escenario, las goroutines con worker pool dan mejor control sobre el uso de CPU. También dejan más explícita la separación entre unidades de trabajo, sincronización y recolección de resultado.\n\nPero Go también cobra precio. Hay que pensar en granularidad de los chunks, consumo de memoria, cancelación, tratamiento de error, backpressure, límites de workers, contención y consistencia del resultado.\n\nSi la división del trabajo es ingenua, la ganancia de CPU puede venir acompañada de estallido de memoria o complejidad innecesaria.\n\n## Contraargumento: Node.js también tiene worker threads\n\nExiste un contraargumento justo: Node.js no está limitado al Event Loop para todo. Los worker threads existen justamente para ejecutar trabajo pesado fuera del hilo principal. También hay estrategias con colas, procesos separados, servicios auxiliares y native addons.\n\nEntonces la comparación honesta no es “Node.js no puede”. Puede.\n\nLa cuestión es costo de implementación, madurez del equipo, observabilidad, integración con el sistema existente y cuánto esfuerzo vale invertir para mantener ese procesamiento dentro del ecosistema Node.",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "loop",
        "mismo",
        "trabajo",
        "más",
        "goroutines",
        "event",
        "concurrencia",
        "problema"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "7011681efcba2713",
      "url": "https://imrafaeldev.site/en/cases/undocumented-monolith-modernization",
      "title": "Flapper: discovering the domain before splitting the monolith (Part 1)",
      "content": "- [Home](/en/)\n- [Cases](/en/cases/)\n- Flapper: discovering the domain before splitting the monolith\n\nFlapper\n\n## Flapper: discovering the domain before splitting the monolith\n\nThe product could not stop, and the original authors were already gone. Before migrating, we had to discover which boundaries the database still revealed.\n\nFull Stack Software Engineer Sep/2021 – Jun/2022\n\n~25% fewer tables in the domain-based split\n\nDescoberta de domínio antes da migração\nO banco legado revela fronteiras; pessoas, autenticação e aeronaves saem gradualmente para contextos com persistência própria.\n\n- [01Context](#context)\n- [02Constraints](#constraints)\n- [03Problem](#problem)\n- [04Decision](#decision)\n- [05Discarded alternative](#discarded-alternative)\n- [06Result](#result)\n- [07Limitations](#limitations)\n\n## Context\n\nAt Flapper, the core product was a PHP monolith over seven years old, with little useful documentation and without the developers who had created it. The application sustained the executive aviation operation and could not be stopped for a rewrite.\n\n## Constraints\n\nCode and database accumulated rules and dependencies that were hard to explain. Changes had unpredictable side effects, and there were no remaining specialists to confirm how each part of the system should evolve. Migration had to coexist with the product in production.\n\n## Problem\n\nSwitching PHP for another technology would not answer the main question: which rules belonged together and which dependencies could be separated without breaking operations. The system needed domain boundaries before new services.\n\n## Decision\n\nI used the existing database and code as a discovery source. Table groupings and relationships helped identify bounded contexts; from there, migration followed the Strangler pattern:\n\n- extract one domain at a time, without stopping the monolith;\n\n- keep in each context only the local representation of the data it needed;",
      "description": "Incremental modernization of an undocumented PHP monolith at Flapper: database as discovery source, bounded contexts, and Strangler. The domain-based split reduced the table count by approximately 25%.",
      "keywords": [
        "domain",
        "monolith",
        "database",
        "imrafaeldev",
        "flapper",
        "before",
        "could",
        "were",
        "context",
        "with"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/cases/undocumented-monolith-modernization"
      }
    },
    {
      "id": "7243598fd022a38b",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 7)",
      "content": "@Post()\n  async createUser(@Body() user: UserDto): Promise<UserEntity> {\n    return this.service.createUser(user.name);\n  }\n\n  @Put(\":id\")\n  async updateUser(\n    @Param(\"id\") id: string,\n    @Body() user: UserDto,\n  ): Promise<UserEntity> {\n    return this.service.updateUser(id, user.name);\n  }\n\n  @Delete(\":id\")\n  async deleteUser(@Param(\"id\") id: string): Promise<void> {\n    return this.service.deleteUser(id);\n  }\n}\n```\n\nDTO:\n\n```typescript\n// infra/dtos/UserDto.ts\nexport class UserDto implements UserEntityProps {\n  id?: string;\n  name: string;\n\n  constructor(props: UserEntityProps) {\n    this.id = props.id;\n    this.name = props.name;\n  }\n}\n```\n\nMódulo de DI:\n\n```typescript\n// infra/modules/UserModule.ts\n@Module({\n  imports: [],\n  controllers: [UserController],\n  providers: [\n    UserService,\n    { provide: CreateUser, useClass: CreateUserUsecase },\n    { provide: UpdateUser, useClass: UpdateUserUsecase },\n    { provide: DeleteUser, useClass: DeleteUserUsecase },\n    { provide: GetUser, useClass: GetUserUsecase },\n    { provide: CreateUserProtocol, useClass: UserPrismaRepository },\n    { provide: UpdateUserProtocol, useClass: UserPrismaRepository },\n    { provide: DeleteUserProtocol, useClass: UserPrismaRepository },\n    { provide: GetUserByNameProtocol, useClass: UserPrismaRepository },\n    { provide: GetUserByIdProtocol, useClass: UserPrismaRepository },\n  ],\n})\nexport class UserModule {}\n```\n\nInfra é mais “livre”: depende do ferramental e existe para sustentar o Core.\n\n## Fluxo de execução\n\nController (Infra) → Service (Adapter) → Usecase (Core) → Protocol (contrato) → Repository (Adapter) → banco (Infra). O usecase não importa o repository concreto; o service não importa a classe concreta do usecase. A seta de dependência aponta para dentro.\n\n## CRUD complementar\n\nAlém do cadastro:\n\n### Busca\n\n```typescript\n// core/features/GetUser.ts\nexport abstract class GetUser {\n  abstract execute(id: string): Promise<UserEntity>;\n}",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "749e767b09e603d7",
      "url": "https://imrafaeldev.site/cases/flapper-modernizacao-en",
      "title": "Flapper: discovering the domain before splitting the monolith (Part 2)",
      "content": "The number measures the table reduction in that domain split, not financial gain, the complete migration, or a market result for the company. The experience end date diverges across older sources; the published period follows the exported profile. If a domain still depends on rules unmapped in the monolith, its extraction must be postponed or given an explicit transitional integration.",
      "description": "Incremental modernization of an undocumented PHP monolith at Flapper: database as discovery source, bounded contexts, and Strangler. The domain-based split reduced the table count by approximately 25%.",
      "keywords": [
        "monolith",
        "database",
        "migration",
        "domain",
        "first",
        "context",
        "with",
        "without",
        "could",
        "rules"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "flapper-modernizacao",
        "slug": "undocumented-monolith-modernization",
        "title": "Flapper: discovering the domain before splitting the monolith",
        "description": "Incremental modernization of an undocumented PHP monolith at Flapper: database as discovery source, bounded contexts, and Strangler. The domain-based split reduced the table count by approximately 25%.",
        "company": "Flapper",
        "role": "Full Stack Software Engineer",
        "period": "Sep/2021 – Jun/2022",
        "excerpt": "The product could not stop, and the original authors were already gone. Before migrating, we had to discover which boundaries the database still revealed.",
        "proofValue": "~25%",
        "proofLabel": "fewer tables in the domain-based split",
        "featuredClaimId": "flapper-domain-modernization",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/flapper-modernizacao-en.md"
      }
    },
    {
      "id": "74aef508c8ec82da",
      "url": "https://imrafaeldev.site/experiences/maxmilhas-en",
      "title": "Maxmilhas | Rafael Pereira",
      "content": "## Context\n\nAt Maxmilhas, I worked in a short and intense period on travel post-sale flows involving cancellations, rebooking, coupons, and customer communication.\n\n## How I worked\n\nI built Node.js, NestJS, and Elixir microservices to automate processes that still depended on support intervention. I implemented eligibility, expiry, and accumulation rules for coupons, along with calculations and validations for cancellation and rebooking flows. Together, those automations reduced the need for manual intervention by 34%.\n\nAnother challenge was integrating a PHP 5.7 monolith, more than ten years old, with the commercial CRM without putting the operational core at risk. I also evolved flight monitoring, notifications, and proactive customer messages.\n\n## What I took from it\n\nI learned to prioritize small, reversible interventions when outcomes need to arrive quickly. Instead of proposing a broad transformation, I focused on points that released operational work.",
      "description": "My experience at Maxmilhas with post-sale automation and legacy integration.",
      "keywords": [
        "worked",
        "flows",
        "rebooking",
        "coupons",
        "customer",
        "that",
        "intervention",
        "with",
        "need",
        "operational"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-maxmilhas",
        "slug": "maxmilhas",
        "title": "Maxmilhas | Rafael Pereira",
        "description": "My experience at Maxmilhas with post-sale automation and legacy integration.",
        "company": "Maxmilhas",
        "role": "Full Stack Software Engineer",
        "period": "Apr 2023 to Oct 2023",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/maxmilhas-en.md"
      }
    },
    {
      "id": "74c887728757eeb0",
      "url": "https://imrafaeldev.site/cases/flapper-modernizacao-pt-br",
      "title": "Flapper: descobrir o domínio antes de separar o monólito (Part 1)",
      "content": "## Contexto\n\nNa Flapper, o produto principal era um monólito PHP com mais de sete anos, pouca documentação útil e sem os desenvolvedores que o haviam criado. A aplicação sustentava a operação de aviação executiva e não podia ser interrompida para uma reescrita.\n\n## Restrições\n\nO código e o banco acumulavam regras e dependências difíceis de explicar. Alterações tinham efeitos colaterais pouco previsíveis, e não havia especialistas remanescentes para confirmar como cada parte do sistema deveria evoluir. A migração precisava coexistir com o produto em produção.\n\n## Problema\n\nTrocar PHP por outra tecnologia não responderia à dúvida principal: quais regras pertenciam juntas e quais dependências poderiam ser separadas sem quebrar a operação. O sistema precisava de fronteiras de domínio antes de serviços novos.\n\n## Decisão\n\nUsei o banco e o código existente como fonte de descoberta. Agrupamentos de tabelas e relações ajudaram a identificar bounded contexts; a partir deles, a migração seguiu o padrão Strangler:\n\n1. extrair um domínio por vez, sem interromper o monólito;\n2. manter em cada contexto apenas a representação local dos dados de que precisava;\n3. propagar mudanças por eventos em Kafka, em vez de conectar todos os serviços ao banco antigo;\n4. usar Node.js, NestJS e Go nos primeiros módulos, com gRPC, REST ou GraphQL conforme o consumidor;\n5. documentar a estratégia e os primeiros módulos para que o time pudesse continuar a transformação.\n\n## Alternativa descartada\n\nReescrever o monólito inteiro, ou manter serviços novos presos ao mesmo banco e às mesmas relações compartilhadas. A primeira opção pararia o negócio; a segunda preservaria o acoplamento que a migração precisava reduzir.\n\n## Resultado\n\nA separação por domínios reduziu em aproximadamente 25% o número de tabelas e criou sete bancos organizados por contexto. Os módulos de pessoas, autenticação e aeronaves foram os primeiros passos de uma transformação planejada para continuar além da entrega inicial.",
      "description": "Modernização incremental de um monólito PHP sem documentação na Flapper: banco como fonte de descoberta, bounded contexts e Strangler. A separação por domínios reduziu em aproximadamente 25% o número de tabelas.",
      "keywords": [
        "não",
        "monólito",
        "para",
        "banco",
        "migração",
        "precisava",
        "contexto",
        "regras",
        "domínio",
        "serviços"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "flapper-modernizacao",
        "slug": "modernizacao-monolito-sem-documentacao",
        "title": "Flapper: descobrir o domínio antes de separar o monólito",
        "description": "Modernização incremental de um monólito PHP sem documentação na Flapper: banco como fonte de descoberta, bounded contexts e Strangler. A separação por domínios reduziu em aproximadamente 25% o número de tabelas.",
        "company": "Flapper",
        "role": "Engenheiro de Software Full Stack",
        "period": "set/2021 a jun/2022",
        "excerpt": "O produto não podia parar, e os autores originais já não estavam lá. Antes de migrar, foi preciso descobrir quais fronteiras o banco ainda revelava.",
        "proofValue": "~25%",
        "proofLabel": "menos tabelas na separação por domínios",
        "featuredClaimId": "flapper-domain-modernization",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/flapper-modernizacao-pt-br.md"
      }
    },
    {
      "id": "74f128002c24a37d",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 5)",
      "content": "Essa fronteira não veio de uma preferência por query única ou por mais lógica na aplicação. Marquei onde CPU, leituras e linhas intermediárias disparavam depois dos filtros seletivos. Então verifiquei se o trecho caro poderia receber fora do banco um conjunto já reduzido.\n\nAntes de mover essa etapa, duas contas precisavam fechar. O banco deveria continuar fazendo o recorte que aproveitava os índices. A aplicação deveria compor o resultado sem buscar linhas demais, criar chamadas por item ou transformar a rede no novo gargalo.\n\nA validação precisava mostrar menos CPU e leituras no banco sem aumentar o payload ou a latência da rota. Sem essa melhora, a divisão apenas transferiria o custo para outra camada e o dashboard continuaria caro.\n\n## Quando o plano aponta para fora do banco\n\nEm outra rota, o plano mostrava uma consulta saudável, com leituras e tempo dentro do esperado. Mesmo assim, a API continuava lenta. Quando medi o fluxo inteiro, o atraso apareceu depois da consulta, no processamento da aplicação e em chamadas remotas executadas em sequência.\n\nUm trecho assim muda a conta.\n\n```typescript\nfor (const item of itens) {\n  await buscarDetalhe(item.id);\n}\n```\n\nCom 50 itens e 40 ms por chamada, o loop pode acrescentar cerca de dois segundos à rota. Cada `await` inicia a próxima chamada somente depois da resposta anterior. Os 40 ms parecem pequenos quando vistos sozinhos. O tempo total mostra as cinquenta esperas em fila.\n\nQuando o profiling aponta esse caminho, mexo no código e no desenho do fluxo. Separo as operações que dependem da resposta anterior daquelas que podem rodar juntas. Para as independentes, testo concorrência limitada.\n\nDisparar cinquenta requisições de uma vez também pode apenas deslocar a pressão para a integração. A mudança precisa ser validada pelo tempo total da rota, pela taxa de erros e pela saturação do serviço remoto. Melhorar uma medida enquanto outra explode não resolve o problema.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "750139aadff7ae38",
      "url": "https://imrafaeldev.site/articles/go-intensivo-es",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 2)",
      "content": "## Goroutines, channels y contexto\n\nUna goroutine no es un hilo dedicado. El runtime la planifica sobre hilos del sistema operativo. Cada goroutine necesita owner, condición de término y alguien que espere su finalización.\n\nLos channels transfieren trabajo u ownership. Los mutexes protegen estado compartido. Un channel con buffer suaviza una diferencia temporal de velocidad; no crea capacidad infinita.\n\n```go\nfunc enqueue(ctx context.Context, jobs chan<- Reading, reading Reading) error {\n\tselect {\n\tcase jobs <- reading:\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\treturn context.Cause(ctx)\n\t}\n}\n```\n\nEl productor cierra un channel cuando sabe que no habrá más envíos. Enviar a un channel cerrado o cerrarlo dos veces causa `panic`. Un channel `nil` bloquea para siempre y deshabilita su caso dentro de `select`.\n\n`context.Context` propaga cancelación, deadlines y metadatos de la request. Recíbelo primero, propágalo, llama a cada `cancel` retornado y no lo guardes en una struct. La cancelación es cooperativa: los loops bloqueantes deben observar `ctx.Done()`.\n\n## Limita la concurrencia antes de que la memoria sea el límite\n\nUna goroutine por mensaje se vuelve costosa cuando un downstream se ralentiza. Crecen las colas y el heap, el GC trabaja más y el proceso puede caer antes de que la CPU parezca llena.\n\n`errgroup` combina espera, propagación del primer error y cancelación compartida. Pon un límite para tareas independientes:\n\n```go\nfunc ProcessBatch(ctx context.Context, batch []Reading) error {\n\tg, ctx := errgroup.WithContext(ctx)\n\tg.SetLimit(16)\n\n\tfor _, reading := range batch {\n\t\treading := reading\n\t\tg.Go(func() error {\n\t\t\treturn processOne(ctx, reading)\n\t\t})\n\t}\n\treturn g.Wait()\n}\n```\n\nClasifica los errores antes de actuar. Una base indisponible puede cancelar un lote. Un payload inválido, duplicado o incompatible con el schema debe ir a cuarentena o DLQ, sin detener el consumer completo.",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "orden",
        "channel",
        "context",
        "https",
        "return",
        "cada",
        "más",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-go-intensive",
        "slug": "go-intensivo",
        "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos",
        "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
        "excerpt": "Una guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 6,
        "sourcePath": "articles/go-intensivo-es.md"
      }
    },
    {
      "id": "76061e010c03e572",
      "url": "https://imrafaeldev.site/es/articulos/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 2)",
      "content": "El primer paso es la interfaz del contrato de la estrategia. En la calculadora, algo que reciba dos números y devuelva el resultado:\n\ninterface BinaryOperationParameters {\nfirstOperand: number;\nsecondOperand: number;\noperator: string;\n}\n\ntype Result = number;\n\ninterface BinaryOperationStrategy {\ncalculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result;\n}\n\n## Implementando estrategias concretas\n\nCada operación matemática se vuelve una clase que implementa BinaryOperationStrategy y ejecuta una única operación. Suma y división:\n\nclass Sum implements BinaryOperationStrategy {\npublic calculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result {\nconst { firstOperand, secondOperand } = parameters;\nreturn firstOperand + secondOperand;\n}\n}\n\nclass Division implements BinaryOperationStrategy {\npublic calculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result {\nconst { firstOperand, secondOperand } = parameters;\nif (secondOperand === 0) {\nthrow new Error(\"Division by zero is not allowed!\");\n}\nreturn firstOperand / secondOperand;\n}\n}\n\n## El Context y la Factory\n\nPara amarrar las estrategias, entran un Context y una Factory (o Analyzer). En ContextAnalyzer, un método evalúa el operador y retorna la Strategy correcta:\n\nclass ContextAnalyzer {\npublic getInstance(operator: string): BinaryOperationStrategy {\nswitch (operator) {\ncase \"*\":\nreturn new Multiplication();\ncase \"+\":\nreturn new Sum();\ncase \"-\":\nreturn new Subtraction();\ncase \"/\":\nreturn new Division();\ncase \"%\":\nreturn new Percent();\ncase \"**\":\nreturn new Pow();\ndefault:\nthrow new Error(\"Operator not found!\");\n}\n}\n}\nEl contexto recibe y ejecuta la estrategia. Conoce solo el contrato, no la implementación concreta. La antigua DefaultCalculator pasa a recibir ese ContextAnalyzer por inyección:\n\nclass Calculator {\nconstructor(private readonly contextAnalyzer: ContextAnalyzer) {}",
      "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "contrato",
        "parameters",
        "operator",
        "cada",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/design-patterns-strategy"
      }
    },
    {
      "id": "766dff8979b3b2bf",
      "url": "https://imrafaeldev.site/en",
      "title": "Rafael Pereira, senior software engineer (Part 2)",
      "content": "### [VBET: analytics on top of a SQL Server we could not change](/en/cases/external-sql-server-analytics/)\n\nThe database belonged to another team. The dashboard had to stop depending on a schema we did not control.\n\n[Open the case](/en/cases/external-sql-server-analytics/)\n\nPipeline de comissões\nCada estágio corresponde a uma decisão incremental documentada no case. Números de outras histórias não entram neste desenho.\n\n[Ver o caso](/en/cases/external-sql-server-analytics/)\n\nFlapper Sep/2021 – Jun/2022\n\n~25% fewer tables in the domain-based split\n\n### [Flapper: discovering the domain before splitting the monolith](/en/cases/undocumented-monolith-modernization/)\n\nThe product could not stop, and the original authors were already gone. Before migrating, we had to discover which boundaries the database still revealed.\n\n[Open the case](/en/cases/undocumented-monolith-modernization/)\n\nDescoberta de domínio antes da migração\nO banco legado revela fronteiras; pessoas, autenticação e aeronaves saem gradualmente para contextos com persistência própria.\n\n[Ver o caso](/en/cases/undocumented-monolith-modernization/)\n\nArtifacts\n\n## Selected projects\n\nPublic repository\n\n### [SMS Manager](/en/projects/sms-manager/)\n\nImporting CSV must not block the Nest API. The campaign persists and publishes to RabbitMQ; consumers in TypeScript, Go, or Rust record the result in Mongo. The API does not wait for the carrier.\n\n[Open the project](/en/projects/sms-manager/)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\nDocumented experiment\n\n### [goc_mcp](/en/projects/goc-mcp/)\n\nMaestro (Codex/Cursor) delegates over MCP; FIFO daemon and OpenCode workers execute with state in SQLite. Orchestration worked, but measurement did not confirm cost or time reduction.\n\n[Open the project](/en/projects/goc-mcp/)",
      "description": "Institutional portfolio and editorial hub for Rafael Pereira. I work at the points where simple systems stop being simple.",
      "keywords": [
        "cases",
        "projects",
        "case",
        "open",
        "with",
        "systems",
        "stop",
        "decision",
        "contact",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/en"
      }
    },
    {
      "id": "76a1785f2fd93a53",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 5)",
      "content": "Validation had to show less database CPU and reads without raising route payload or latency. Without that improvement, the split would only transfer cost to another layer and the dashboard would stay expensive.\n\n## When the plan points outside the database\n\nOn another route, the plan showed a healthy query, with reads and time within expectations. Still, the API stayed slow. When I measured the whole flow, the delay appeared after the query, in application processing and sequential remote calls.\n\nA stretch like this changes the math.\n\n```typescript\nfor (const item of items) {\n  await fetchDetail(item.id);\n}\n```\n\nWith 50 items and 40 ms per call, the loop can add about two seconds to the route. Each `await` starts the next call only after the previous response. The 40 ms looks small alone. Total time shows fifty waits in a queue.\n\nWhen profiling points at that path, I change the code and the flow design. I separate operations depending on the previous response from those that can run together. For the independent ones, I test bounded concurrency.\n\nFiring fifty requests at once can also just move pressure to the integration. The change must be validated by total route time, error rate, and remote-service saturation. Improving one measure while another explodes does not fix the problem.\n\n## Two hours to locate the layer, one day to test\n\n\"We found the bottleneck\" too often becomes \"we fixed the slowness\" too early. After locating a bad query or sequential stretch, the change, deploy, and production confirmation are still missing. Finding the layer only shortens the suspect list.\n\nIn the first two hours, I want to locate where to dig deeper: database, application, integration, or infrastructure. I open the plan, compare time and reads, count queries, and time the stretch running after the database.",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "76e0195ab3cb69f7",
      "url": "https://imrafaeldev.site/casos",
      "title": "Estudos de caso",
      "content": "- [Início](/)\n- Casos\n\n## Estudos de caso\n\nCada case documenta a restrição, a decisão, a alternativa descartada e o resultado medido. Também registra quando a decisão deve ser revista.\n\n- Infosistemas fev/2025 a mai/2026\n\n## [Infosistemas: contrato de falha na mensageria RabbitMQ](/casos/mensageria-rabbitmq/)\n\nFalhas intermitentes entre microsserviços sem contrato para retry, DLQ ou duplicidade. Mais consumidores só empurravam a sobrecarga.\n\n**~98%** redução de falhas intermitentes nos fluxos críticos\n\n[Abrir o case — Infosistemas: contrato de falha na mensageria RabbitMQ](/casos/mensageria-rabbitmq/)\n\n- VBET out/2023 a fev/2025\n\n## [VBET: analytics sobre um SQL Server que não podíamos mudar](/casos/analytics-sql-server-externo/)\n\nO banco era de outro time. O dashboard precisava deixar de depender de um schema que não controlávamos.\n\n**~7 min → <1 s** comissões, da carga original ao cache quente\n\n[Abrir o case — VBET: analytics sobre um SQL Server que não podíamos mudar](/casos/analytics-sql-server-externo/)\n\n- Flapper set/2021 a jun/2022\n\n## [Flapper: descobrir o domínio antes de separar o monólito](/casos/modernizacao-monolito-sem-documentacao/)\n\nO produto não podia parar, e os autores originais já não estavam lá. Antes de migrar, foi preciso descobrir quais fronteiras o banco ainda revelava.\n\n**~25%** menos tabelas na separação por domínios\n\n[Abrir o case — Flapper: descobrir o domínio antes de separar o monólito](/casos/modernizacao-monolito-sem-documentacao/)",
      "description": "Cada case documenta a restrição, a decisão, a alternativa descartada e o resultado medido. Também registra quando a decisão deve ser revista.",
      "keywords": [
        "casos",
        "não",
        "case",
        "infosistemas",
        "contrato",
        "abrir",
        "vbet",
        "flapper",
        "descobrir",
        "antes"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/casos"
      }
    },
    {
      "id": "797fdf076f083685",
      "url": "https://imrafaeldev.site/experiences/sustentec-pt-br",
      "title": "Sustentec | Rafael Pereira",
      "content": "## O contexto\n\nNa Sustentec, trabalhei em sistemas ligados a laboratórios, pesquisa e desenvolvimento. A experiência combinou a manutenção de um produto existente com a evolução de funcionalidades e integrações.\n\n## Como atuei\n\nDesenvolvi uma API REST em Dart com Shelf para integrar bases de instituições de pesquisa. Também mantive e evoluí um sistema de gestão de laboratórios com Java, Spring Boot, JPA, Hibernate, PostgreSQL e Angular.\n\nImplementei testes de integração onde antes não havia essa cobertura, desenvolvi relatórios e entregas ponta a ponta e participei da coleta de requisitos com clientes e do refinamento de sprints com o Product Owner.\n\n## O que levo\n\nEssa experiência reforçou que qualidade não se limita a testes unitários. Em sistemas com várias camadas, eu precisava validar o comportamento real entre API, persistência e interface.",
      "description": "Minha experiência na Sustentec com sistemas de pesquisa, APIs e qualidade.",
      "keywords": [
        "sistemas",
        "laboratórios",
        "pesquisa",
        "experiência",
        "desenvolvi",
        "testes",
        "não",
        "essa",
        "ponta",
        "contexto"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-sustentec",
        "slug": "sustentec",
        "title": "Sustentec | Rafael Pereira",
        "description": "Minha experiência na Sustentec com sistemas de pesquisa, APIs e qualidade.",
        "company": "Sustentec",
        "role": "Engenheiro de Software Full Stack Pleno",
        "period": "jan/2021 a ago/2021",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/sustentec-pt-br.md"
      }
    },
    {
      "id": "7a2b3aea54bfcb5b",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 5)",
      "content": "**Mecanismo.** Concorrência limitada impõe um teto de trabalhos simultâneos com semáforo (canal com buffer de vagas), pool de workers ou `errgroup.Group` com limite. Backpressure é o efeito: quando o teto é atingido, novos trabalhos esperam em vez de consumir memória, conexões e descritores de arquivo sem controle. O tamanho do buffer do canal e o número de workers são parâmetros de capacidade, não detalhes de implementação.\n\n**Falha ou limite que ele trata.** Sem limite, um pico de entrada cria uma goroutine por item, esgota conexões com o banco, estoura memória com buffers acumulados e derruba o processo. O limite do mecanismo: teto baixo demais subutiliza recursos e aumenta latência de fila; teto alto demais apenas desloca o gargalo para o serviço seguinte.\n\n**Exemplo de aplicação.** Cenário didático: buscar URLs com no máximo 3 requisições simultâneas.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\nfunc fetchURL(ctx context.Context, url string) error {\n\tselect {\n\tcase <-time.After(100 * time.Millisecond):\n\t\tfmt.Println(\"ok:\", url)\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\t}\n}\n\nfunc main() {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\turls := []string{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"}\n\n\tconst maxInflight = 3\n\tsem := make(chan struct{}, maxInflight)\n\n\tvar wg sync.WaitGroup\nloop:\n\tfor _, u := range urls {\n\t\tselect {\n\t\tcase sem <- struct{}{}: // adquire a vaga antes de iniciar a goroutine\n\t\tcase <-ctx.Done():\n\t\t\tbreak loop\n\t\t}\n\t\twg.Add(1)\n\t\tgo func(url string) {\n\t\t\tdefer wg.Done()\n\t\t\tdefer func() { <-sem }() // libera vaga\n\t\t\t_ = fetchURL(ctx, url)\n\t\t}(u)\n\t}\n\twg.Wait()\n}\n```",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "7a7806d136104c7f",
      "url": "https://imrafaeldev.site/experiences/azify-en",
      "title": "Azify | Rafael Pereira",
      "content": "## Context\n\nAt Azify, I worked as a consultant on financial infrastructure for fintechs and smaller banks. The product covered capabilities such as Pix, transfers, cards, digital wallets, and other banking services.\n\n## How I worked\n\nI contributed to architectural decisions and helped establish engineering practices for an environment where consistency, security, and stability had direct financial impact. I built a NestJS settlement engine with multi-exchange integrations, risk monitoring, and compliance controls.\n\nI also worked on exchange and blockchain integrations for transactional flows and on a multi-tenant BaaS platform using OAuth 2.0, JWT, and encryption. Through query profiling, index review, and Redis optimization, I reduced latency in critical financial APIs by 30%.\n\n## What I took from it\n\nThis consulting work brought recurring parts of my trajectory, including payments, multi-tenancy, and transactional systems, into a setting with even greater responsibility for authorization and consistency.",
      "description": "My consulting work at Azify with settlement, BaaS, and financial services.",
      "keywords": [
        "worked",
        "financial",
        "consistency",
        "with",
        "integrations",
        "transactional",
        "context",
        "azify",
        "consultant",
        "infrastructure"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-azify",
        "slug": "azify",
        "title": "Azify | Rafael Pereira",
        "description": "My consulting work at Azify with settlement, BaaS, and financial services.",
        "company": "Azify",
        "role": "Senior Backend Engineer — Consulting",
        "period": "Mar 2025 to Jun 2025",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/azify-en.md"
      }
    },
    {
      "id": "7b0a20fceaf3bb19",
      "url": "https://imrafaeldev.site/projects/md2cv-pt-br",
      "title": "md2cv (Part 2)",
      "content": "Repositório público sob MIT, portal de documentação e releases x64 para Linux (AppImage, `.deb`, `.rpm`) e Windows (NSIS e portátil), com CI e testes Vitest e Playwright em persistência, agentes, ATS e fluxos Electron.\n\nO export profissional usado neste site nasce desse produto e não é lido pelo site em runtime.\n\n## Limitações\n\nNão substitui revisão humana nem promete contratação ou aprovação automática por plataformas de recrutamento. A adaptação a vagas depende de respostas confirmadas; o sistema recusa fabricar experiência.\n\nBinários Windows desta fase não possuem assinatura de código. O projeto é autoral e sem finalidade comercial do mantenedor; a licença MIT permite reutilização nos seus termos.",
      "description": "Estúdio desktop local-first para perfil profissional, currículos Markdown, versões imutáveis, ATS e adaptação a vagas com agentes supervisionados. Os dados ficam no SQLite da máquina.",
      "keywords": [
        "não",
        "currículo",
        "versão",
        "candidatura",
        "grafo",
        "agentes",
        "perfil",
        "produto",
        "entre",
        "experiência"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "projeto-md2cv",
        "slug": "md2cv",
        "title": "md2cv",
        "description": "Estúdio desktop local-first para perfil profissional, currículos Markdown, versões imutáveis, ATS e adaptação a vagas com agentes supervisionados. Os dados ficam no SQLite da máquina.",
        "excerpt": "Perfil e versões imutáveis ficam no SQLite da máquina. O agente supervisionado só propõe alterações sob schema; ATS e exportação em PDF/DOCX reutilizam o mesmo grafo, sem backend SaaS dono dos dados.",
        "repoUrl": "https://github.com/imrafaeldev/md2cv",
        "status": "Produto próprio, desktop",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "projects/md2cv-pt-br.md"
      }
    },
    {
      "id": "7cfbe964cf8126cd",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-pt-br",
      "title": "Design Patterns: Strategy (Part 2)",
      "content": "```typescript\ninterface BinaryOperationParameters {\n  firstOperand: number;\n  secondOperand: number;\n  operator: string;\n}\n\ntype Result = number;\n\ninterface BinaryOperationStrategy {\n  calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result;\n}\n```\n\n## Implementando estratégias concretas\n\nCada operação matemática vira uma classe que implementa `BinaryOperationStrategy` e executa uma única operação. Soma e divisão:\n\n```typescript\nclass Sum implements BinaryOperationStrategy {\n  public calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result {\n    const { firstOperand, secondOperand } = parameters;\n    return firstOperand + secondOperand;\n  }\n}\n\nclass Division implements BinaryOperationStrategy {\n  public calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result {\n    const { firstOperand, secondOperand } = parameters;\n    if (secondOperand === 0) {\n      throw new Error(\"Division by zero is not allowed!\");\n    }\n    return firstOperand / secondOperand;\n  }\n}\n```\n\n## O Context e a Factory\n\nPara amarrar as estratégias, entram um Context e uma Factory (ou Analyzer). Em `ContextAnalyzer`, um método avalia o operador e retorna a Strategy correta:\n\n```typescript\nclass ContextAnalyzer {\n  public getInstance(operator: string): BinaryOperationStrategy {\n    switch (operator) {\n      case \"*\":\n        return new Multiplication();\n      case \"+\":\n        return new Sum();\n      case \"-\":\n        return new Subtraction();\n      case \"/\":\n        return new Division();\n      case \"%\":\n        return new Percent();\n      case \"**\":\n        return new Pow();\n      default:\n        throw new Error(\"Operator not found!\");\n    }\n  }\n}\n```",
      "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "parameters",
        "operator",
        "cada",
        "strategy",
        "operação",
        "calculate"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
        "excerpt": "Cadeias de if/else crescem e ficam frágeis. O Strategy isola cada algoritmo atrás de um contrato.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-pt-br.md"
      }
    },
    {
      "id": "7db517008408b780",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 3)",
      "content": "That radar only picks the first measurement. The next investigation layer comes from the math.\n\n## One correct line can waste the index\n\nOne of those lines often passes review unnoticed. It returns a full day's records.\n\n```sql\nWHERE CAST(datetime_column AS date) = @date\n```\n\nThe screen result looks correct. In the plan, the story may differ. Applying `CAST` to the column forces the query to transform values before comparison. With an index on `datetime_column`, that can prevent a direct range seek, raise reads substantially, and even lead to a scan.\n\nWhen the request is for a full day's records, I compute the boundaries outside the column.\n\n```sql\nWHERE datetime_column >= @start_of_day\n  AND datetime_column < @start_of_next_day\n```\n\nIf the start is July 16 at midnight, the next bound is July 17 at midnight. That includes every value on the 16th, including those with fractional seconds at the end, without depending on `23:59:59.999`.\n\nThe result stays correct, but now there is a range the index can walk. Confirmation comes from comparing reads and the access operator in both plans. If the metric does not change, the hypothesis did not hold.\n\n## Read the plan by the work sequence\n\nAfter comparing filter versions, I read the plan as a story of the work produced by the query. I start with total time and reads. If the screen returns ten indicators but execution performs hundreds of thousands of reads, there is a bill to explain.\n\nThen I compare estimated vs actual cardinality. When the optimizer expected few rows and received many, it may have picked joins, memory, and aggregations for a far smaller scenario than it met at runtime.\n\nNext I follow where data grows. I look for the operator where a JOIN takes thousands of rows to hundreds of thousands, just before a filter or `GROUP BY` shrinks everything again. The small final answer may hide an enormous intermediate volume. That is where aggregations and sorts start spending too much CPU.",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "7dec2acbb7e211e9",
      "url": "https://imrafaeldev.site/experiences/south-system-quiq-itau-en",
      "title": "South System, QUIQ, and Itaú | Rafael Pereira",
      "content": "## Context\n\nAt South System, I was assigned to QUIQ to work on a Marketplace as a Service for financial institutions. Itaú was the first context, but the product needed to accept new banks without requiring a fork for each client.\n\n## How I worked\n\nI participated in architecture, data modeling, technology choices, and rule refinement with product. The result was a white-label, multi-tenant platform with logical tenant isolation and Hexagonal Architecture to keep the domain apart from specific integrations.\n\nI worked with Node.js, TypeScript, MySQL, asynchronous Go services, and AWS. I structured unit and integration testing for critical cases and used static analysis as part of the quality workflow.\n\n## What I took from it\n\nThis experience changed how I communicate architecture. I started treating alignment with product, the Product Owner, and stakeholders as part of the technical decision rather than a later step after code.",
      "description": "My experience with a white-label, multi-tenant marketplace for financial institutions.",
      "keywords": [
        "product",
        "with",
        "architecture",
        "context",
        "worked",
        "from",
        "part",
        "south",
        "system",
        "assigned"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-south-system-quiq-itau",
        "slug": "south-system-quiq-itau",
        "title": "South System, QUIQ, and Itaú | Rafael Pereira",
        "description": "My experience with a white-label, multi-tenant marketplace for financial institutions.",
        "company": "South System (assigned to QUIQ/Itaú)",
        "role": "Backend Engineer",
        "period": "Jun 2022 to Apr 2023",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/south-system-quiq-itau-en.md"
      }
    },
    {
      "id": "7ee787f97503fe63",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 8)",
      "content": "**Quando escolher outra abordagem.** Se o efeito for naturalmente idempotente (escrita com `SET` pela chave, upsert com versão), a chave de idempotência pode ser a própria chave de negócio. Se a ordem global for requisito real — e não apenas conveniência — reduza o paralelismo a um único consumidor ou reparticione por chave; o custo é vazão menor. Em produção, troque a simulação em memória por armazenamento durável: a verificação de idempotência e a confirmação (claim) precisam ser atômicas e duráveis, e a ordem por chave exige partição por chave com um único worker ativo por partição.\n\n## 6. Shutdown gracioso\n\n**Mecanismo.** Shutdown gracioso converte um sinal de término (`SIGINT`/`SIGTERM`) em uma sequência ordenada: parar de aceitar trabalho novo, cancelar contextos, aguardar trabalhos em voo com timeout e só então encerrar recursos (servidor HTTP, consumidores, pools de conexão). Em Go, o padrão combina `signal.NotifyContext` (ou `os/signal`), `http.Server.Shutdown` e `sync.WaitGroup` para acompanhar goroutines de fundo.\n\n**Falha ou limite que ele trata.** Sem essa sequência, o processo morre no meio de requisições e confirmações: clientes recebem conexões cortadas, mensagens voltam para a fila sem controle e arquivos/escritas ficam pela metade. O limite: o tempo de graça é finito — trabalhos que ignoram o contexto de shutdown estouram o timeout e são interrompidos de qualquer forma, então cada etapa precisa observar o cancelamento.\n\n**Exemplo de aplicação.** Cenário didático limitado a servidor HTTP sem workers de fundo: drena apenas conexões HTTP ao receber `Ctrl+C`. Workers de fundo exigiriam `WaitGroup`/drenagem adicional, não cobertos aqui.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n)\n\nfunc main() {\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = w.Write([]byte(\"ok\"))\n\t})",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 7,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "7f2a13f08a17d3d3",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 7)",
      "content": "Por eso, en el primer día quiero al menos una hipótesis probada con medida de antes y después. La prueba puede reescribir el filtro sin `CAST`, dividir la query o limitar la concurrencia del loop. Después del deploy, comparo la métrica que motivó el cambio: lecturas y CPU para la base; latencia, errores y saturación para la ruta.\n\nEn fallos intermitentes, esa ventana crece. Hay que esperar que el síntoma reaparezca con métricas suficientes para comparar.\n\n## La confirmación ocurre después del deploy\n\nDespués de algunos errores en producción, dejé de buscar un culpable estándar. Base, aplicación e infraestructura entran como hipótesis, cada una acompañada de una medida que puede confirmarla o descartarla.\n\nCuando el plan muestra lecturas altas y cardinalidad distante de la realidad, trabajo en la consulta. Cuando la consulta responde bien y el profiling concentra tiempo después de ella, sigo hacia el código o hacia la integración.\n\nCaché, cola, índice y máquina mayor siguen disponibles. No son decisiones prohibidas. Solo no deberían entrar antes de saber qué trabajo está reteniendo la respuesta.\n\nAntes de cambiar, defino la medida que debe mejorar y la que no puede empeorar. Después del deploy, vuelvo al plan, a las métricas y al profiling. Si la métrica objetivo no mejora, la hipótesis no se sostuvo. Si la medida de protección empeora, la solución creó otro problema.\n\nDescarto la hipótesis y regreso al punto del flujo en que el tiempo aún crece.\n\n---\n\n*Publicado originalmente en [LinkedIn](https://www.linkedin.com/pulse/maioria-dos-problemas-de-performance-backend-come%C3%A7a-perto-s-pereira-zqlae/) el 16 de julio de 2026.*",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "7f73464db910b9d4",
      "url": "https://imrafaeldev.site/es/proyectos/md2cv",
      "title": "md2cv (Part 1)",
      "content": "- [Inicio](/es/)\n- [Proyectos](/es/proyectos/)\n- md2cv\n\nProducto propio, desktop\n\n## md2cv\n\nPerfil y versiones inmutables quedan en el SQLite de la máquina. El agente supervisado solo propone cambios bajo schema; ATS y exportación en PDF/DOCX reutilizan el mismo grafo, sin backend SaaS dueño de los datos.\n\n[Repositorio](https://github.com/imrafaeldev/md2cv)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\n- [01Problema](#problema)\n- [02Restricciones](#restricciones)\n- [03Decisión](#decision)\n- [04Estado actual](#estado-actual)\n- [05Limitaciones](#limitaciones)\n\n## Problema\n\nLos perfiles profesionales se esparcen entre docs, LinkedIn y exports. Cada candidatura pide un ángulo distinto. Adaptar el currículo con un LLM sin frontera inventa experiencia y borra el contexto de la versión anterior.\n\nSe necesita un grafo versionado en la máquina que pregunte lo que falta y rechace salida incompleta, sin prometer contratación ni aprobación automática por ATS.\n\n## Restricciones\n\nDesktop y local-first (Electron): sin cuenta propietaria y sin backend dueño de los datos.\n\n- IPC tipado y validado entre renderer y proceso principal.\n\n- SQLite con foreign keys, WAL, migraciones con checksum, verificaciones de integridad y backup antes de operación destructiva.\n\n- Agentes (Codex, Cursor, OpenCode) entran por adaptadores aislados, no como dueños de la base.\n\n- La adaptación a una candidatura no graba en el perfil sin confirmación explícita.\n\n- Acceso externo solo bajo acción del usuario (búsqueda de URL de empresa o CLI de IA ya configurada en el equipo); el producto no almacena credenciales de proveedores.\n\n## Decisión\n\nRenderer React/Vite separado del proceso principal. El producto organiza el trabajo en un único grafo local:\n\n- Perfil: experiencias, formación, cursos, idiomas, proyectos, enlaces, habilidades y empresas por persona.",
      "description": "Estudio desktop local-first para perfil profesional, currículos Markdown, versiones inmutables, ATS y adaptación a vacantes con agentes supervisados. Los datos quedan en el SQLite de la máquina.",
      "keywords": [
        "grafo",
        "perfil",
        "producto",
        "agente",
        "bajo",
        "candidatura",
        "currículo",
        "versión",
        "proyectos",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/es/proyectos/md2cv"
      }
    },
    {
      "id": "7fea0f7f91b97b1e",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 7)",
      "content": "Antes de otimizar, estabeleça um SLO, reproduza a carga e meça. `pprof` mostra CPU, heap, bloqueio e contenção; `go tool trace` ajuda a observar scheduler e latências de bloqueio. Benchmarks isolam a mudança.\n\n```bash\ngo test ./...\ngo test -race ./...\ngo test -bench=. -benchmem ./...\ngo test -run=^$ -bench=BenchmarkDecode -cpuprofile=cpu.out -memprofile=mem.out ./...\ngo tool pprof cpu.out\ngo build -gcflags=\"-m=2\" ./...\n```\n\nPré-alocar um slice com capacidade conhecida reduz realocações. Evite conversões repetidas entre `string` e `[]byte` em caminho quente. `sync.Pool` é um cache oportunista para temporários, não uma reserva de capacidade. Pooling sem profile aumenta a complexidade e pode reter memória sem ganho.\n\nGenerics funcionam bem para algoritmos que preservam as mesmas operações entre tipos. Interfaces continuam melhores para comportamento dinâmico e fronteiras. Não transforme DTOs e serviços de domínio em abstrações genéricas sem um ganho claro.\n\n## Perguntas que valem uma entrevista\n\n**Goroutine é thread?** Não. É uma unidade leve gerenciada pelo runtime e multiplexada sobre threads do sistema operacional.\n\n**Channel ou mutex?** Use channel para transferir trabalho ou ownership; use mutex para proteger estado compartilhado e invariantes.\n\n**Quem fecha um channel?** O produtor que sabe que não haverá mais envios. O receiver normalmente não fecha.\n\n**Como preservar ordem com vários workers?** Não preserve ordem global sem necessidade. Particione por uma chave, como `device_id`, e mantenha cada partição sequencial.\n\n**Como lidar com duplicatas?** Use uma chave de idempotência estável, deduplicação transacional quando possível e operações naturalmente idempotentes, como upsert com versão ou sequência.\n\n**Como parar um consumer no Kubernetes?** Receba `SIGTERM`, remova readiness, pare fetch, drene o trabalho em voo dentro do grace period, confirme apenas o concluído e feche recursos.",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "8142b4466e5cc115",
      "url": "https://imrafaeldev.site",
      "title": "Rafael Pereira, engenheiro de software sênior (Part 4)",
      "content": "Contato\n\n## Tem um sistema que deixou de ser simples?\n\nChame direto pelo @imrafaeldev, sem formulário — para conversa profissional, comece pelo LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/) [YouTube](https://www.youtube.com/@imrafaeldev) [GitHub](https://github.com/imrafaeldev) [LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir a página de contato](/contato/)",
      "description": "Portfólio institucional e hub editorial de Rafael Pereira. Trabalho nos pontos em que sistemas simples deixam de ser simples.",
      "keywords": [
        "não",
        "casos",
        "projetos",
        "abrir",
        "case",
        "decisão",
        "caso",
        "falhas",
        "contato",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/"
      }
    },
    {
      "id": "835b19ede87a03e7",
      "url": "https://imrafaeldev.site/projects/post-engine-es",
      "title": "Post Engine",
      "content": "## Problema\n\nGenerar un post \"profesional\" con LLM a partir de un eslogan inventa biografía. El flujo necesita entrevistar, identificar lagunas, preparar el briefing, redactar y exportar, y rechazar lo no confirmado.\n\n## Restricciones\n\nNúcleo en Python con fronteras entre entrevista, generación, preservación de autoría, segmentación y persistencia. Llamadas a modelo en workspace aislado, allowlist de proveedor y prompts como contratos versionados. Evaluación híbrida: LLM más heurísticas deterministas. La ausencia de experiencia nunca se vuelve falsa vivencia.\n\n## Decisión\n\nEntrevistas adaptativas, briefing autoral, storyboard, veto a contenido fabricado, registry SQLite de prompts con rollback, interfaz textual y frontend React/Vite para revisar fases. Exportación Markdown o SlideMark JSON tras la evaluación.\n\n## Estado actual\n\nBase con pruebas de entrevista, aislamiento de LLM, registry, persistencia y conversión SlideMark.\n\n## Limitaciones\n\nNo es un generador genérico de thought leadership. Sin repertorio real, el sistema se niega; no completa la biografía.",
      "description": "Estación editorial centrada en autoría: entrevista, briefing, storyboard y exportación después de que el gateway bloquee contenido fabricado. Prompts versionados; workspace LLM aislado.",
      "keywords": [
        "biografía",
        "briefing",
        "entrevista",
        "persistencia",
        "prompts",
        "evaluación",
        "registry",
        "slidemark",
        "problema",
        "generar"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "projeto-post-engine",
        "slug": "post-engine",
        "title": "Post Engine",
        "description": "Estación editorial centrada en autoría: entrevista, briefing, storyboard y exportación después de que el gateway bloquee contenido fabricado. Prompts versionados; workspace LLM aislado.",
        "excerpt": "La entrevista adaptativa extrae evidencias; el gateway híbrido (LLM + heurística) bloquea vivencia inventada. Solo el contenido confirmado sigue a borrador y exportación en Markdown o SlideMark.",
        "repoUrl": "https://github.com/imrafaeldev/post-engine",
        "status": "Estación de trabajo editorial",
        "featured": "false",
        "order": "5",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/post-engine-es.md"
      }
    },
    {
      "id": "83e135ceb249b950",
      "url": "https://imrafaeldev.site/es/articulos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia\n\n## Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia\n\nNode.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.\n\n24 de junio de 2026\n\n- [Trade-offs](/es/articulos/?topic=trade-offs)\n- [Rendimiento](/es/articulos/?topic=performance)\n\nLa tesis es simple: Node.js con Event Loop y Go con goroutines no resuelven el mismo tipo de problema del mismo modo. La comparación se vuelve mala cuando tratamos ambos como competidores directos en cualquier escenario. En la práctica, el error más común es confundir concurrencia con paralelismo.\n\nConcurrencia es organizar varias tareas que pueden estar en curso al mismo tiempo. Paralelismo es ejecutar trabajo de hecho al mismo tiempo, usando múltiples núcleos de CPU. Esa diferencia parece académica hasta que aparece en producción.\n\nEl Event Loop de Node.js es muy bueno cuando el cuello de botella está en la espera: API externa, base de datos, WebSocket, input de usuario, colas y eventos. Mientras una operación aguarda respuesta, el loop sigue atendiendo otras tareas. Es el dueño de la bodega en el mostrador: no para porque pidió a alguien buscar rapadura en el depósito.\n\nLas goroutines, por otro lado, empiezan a volverse más interesantes cuando el trabajo es CPU bound, divisible y puede aprovechar múltiples núcleos con control explícito de concurrencia. Son unidades ligeras de ejecución gestionadas por el runtime de Go. Con ellas, se puede romper una tarea en partes menores, distribuir la ejecución y sincronizar el resultado al final.\n\nEs más parecido a un puesto lleno en el São João de Caruaru: una persona asa el maíz, otra revuelve la canjica, otra corta el bolo de rolo. El trabajo avanza al mismo tiempo, con cada persona cuidando una parte.",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "loop",
        "mismo",
        "trabajo",
        "más",
        "goroutines",
        "event",
        "concurrencia",
        "problema"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "84c21c44b6d3655d",
      "url": "https://imrafaeldev.site/en/articles/go-intensive",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 5)",
      "content": "go test -race ./...\ngo test -bench=. -benchmem ./...\ngo tool pprof cpu.out\ngo tool trace trace.out\nG is a goroutine, M an OS thread, and P a logical execution resource. GOMAXPROCS limits Ps that run Go code concurrently, not the goroutine count. Goroutines are lightweight, not free. Profile before pooling or micro-optimizing. Preallocate known slice capacity, avoid repeated string/[]byte conversions in hot paths, and treat sync.Pool as an opportunistic temporary-object cache.\n\n## Interview answers to keep ready\n\n**Is a goroutine a thread?** No. It is a lightweight runtime-managed execution unit multiplexed over OS threads.\n\n**Channel or mutex?** Use a channel to transfer work or ownership; use a mutex to protect shared state and invariants.\n\n**Who closes a channel?** The",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "reading",
        "context",
        "channel",
        "errors",
        "return",
        "value",
        "device",
        "goroutine",
        "work",
        "articles"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/en/articles/go-intensive"
      }
    },
    {
      "id": "84e646d1d0d36fd0",
      "url": "https://imrafaeldev.site/es/articulos/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 5)",
      "content": "async delete(id: string): Promise&#x3C;void> {\nreturn this.db.users.delete(id);\n}\n}\nMock del conector:\n\nexport const mockDbConnector: DbConnector = {\nusers: {\ngetById: async (id: string) =>\nPromise.resolve(new UserEntity({ id, name: \"Test\" })),\ngetByName: async (name: string) =>\nPromise.resolve(new UserEntity({ id: \"1\", name })),\nregister: async (name: string) =>\nPromise.resolve(new UserEntity({ id: \"2\", name })),\nupdate: async (id: string, name: string) =>\nPromise.resolve(new UserEntity({ id, name })),\ndelete: async (_id: string) => Promise.resolve(),\n},\nprofiles: {\ngetById: async (_id: string) =>",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "regla",
        "export",
        "architecture",
        "para",
        "class"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/typescript-clean-architecture"
      }
    },
    {
      "id": "8550454b96d817f9",
      "url": "https://imrafaeldev.site/articles/go-intensivo-es",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 1)",
      "content": "Esta guía es para quien ya trabaja con backend y necesita volver a conversar o implementar servicios Go de producción. Las decisiones que más importan son concurrencia limitada, cancelación, colas finitas, idempotencia y observabilidad.\n\nEl recorte usa Go 1.26. No es una introducción al lenguaje. Repasa la sintaxis rápido y dedica el tiempo a las decisiones que cambian el diseño de un consumer, una API o un pipeline de telemetría.\n\n## Ruta de 30 minutos\n\n| Tiempo | Bloque | Prioridad |\n| --- | --- | --- |\n| 0-4 min | Tipos, structs, interfaces y errores | Revisión rápida |\n| 4-10 min | Goroutines, channels, `select` y contexto | Alta |\n| 10-17 min | Concurrencia limitada y backpressure | Máxima |\n| 17-23 min | Pipeline IoT resiliente | Máxima |\n| 23-26 min | Runtime, memoria y profiling | Alta |\n| 26-30 min | Arquitectura y entrevista | Máxima |\n\n## Fundamentos que aparecen en producción\n\nGo favorece composición, contratos pequeños y flujo explícito. Un valor puede llevar su propia validación sin depender de un framework:\n\n```go\nvar ErrOutOfRange = errors.New(\"reading out of range\")\n\ntype Reading struct {\n\tDeviceID string\n\tSequence uint64\n\tValue    float64\n}\n\nfunc (r Reading) Validate() error {\n\tif r.DeviceID == \"\" {\n\t\treturn errors.New(\"device_id is required\")\n\t}\n\tif r.Value < -100 || r.Value > 250 {\n\t\treturn fmt.Errorf(\"%w: %.2f\", ErrOutOfRange, r.Value)\n\t}\n\treturn nil\n}\n```\n\nEl zero value suele ser útil. Los slices comparten backing array hasta que `append` realoca; los maps no tienen orden de iteración y necesitan sincronización para acceso concurrente. Un `string` contiene bytes inmutables, generalmente UTF-8. `defer` se ejecuta en orden LIFO, pero evalúa sus argumentos al registrarse.\n\nLas interfaces se satisfacen implícitamente. Define interfaces pequeñas donde se consumen. Los errores son valores: añade contexto con `%w` e inspecciona la cadena con `errors.Is` o `errors.As`. Reserva `panic` para invariantes rotas o fallos irrecuperables de arranque.",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "orden",
        "channel",
        "context",
        "https",
        "return",
        "cada",
        "más",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-go-intensive",
        "slug": "go-intensivo",
        "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos",
        "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
        "excerpt": "Una guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 6,
        "sourcePath": "articles/go-intensivo-es.md"
      }
    },
    {
      "id": "855a299ff13604dd",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-container-with-most-water",
      "title": "Desenoxidando la lógica #02: Container With Most Water (Part 3)",
      "content": "El detalle del avance importa. En la versión que yo había escrito, los punteros solo avanzaban cuando el área actual no era mayor que maxArea. Si se encontraba una nueva área máxima, la misma combinación se calculaba de nuevo, sin salir del loop. La corrección fue separar las dos decisiones: registrar el área y, después, mover el puntero de la menor altura.\n\n## El resultado de los intentos\n\nEl historial de LeetCode quedó así:\n\n- JavaScript: aceptada, 3 ms y 63.6 MB.\n\n- JavaScript: respuesta incorrecta.\n\n- JavaScript: respuesta incorrecta.\n\n- Go: aceptada, 0 ms y 9.6 MB.\n\n- TypeScript: aceptada, 3 ms y 63.9 MB.\n\n- Go: respuesta incorrecta.\n\nFueron tres intentos con respuesta incorrecta antes de llegar a las soluciones aceptadas en JavaScript, Go y TypeScript. Más que contar envíos, yo quería mirar el error, entender la hipótesis que falló e intentarlo de nuevo sin tercerizar todo el razonamiento.\n\n## Complejidad\n\nEl algoritmo recorre el array una vez. En cada iteración, uno de los punteros avanza, entonces la complejidad de tiempo es O(n) y la complejidad de espacio es O(1).\n\nEl primer intento también usaba dos punteros, pero hacía trabajo extra para comparar posibilidades futuras. La segunda solución usa una propiedad del problema para descartar con seguridad parte de las combinaciones.\n\nEse fue el ejercicio esta vez: no confundir una elección que parece buena ahora con una decisión que el problema realmente permite justificar.\n\n---\n\n*Serie Desenoxidando la lógica #02 — Container With Most Water. Problema en [leetcode.com/problems/container-with-most-water](https://leetcode.com/problems/container-with-most-water/).*\n\nGeometria: min da altura × largura\nNas posições 1 e 8, a menor altura é 7 e a largura é 7. O recipiente produz área 49.\n\nDois ponteiros: mova a menor linha\nA largura sempre diminui. Só mover a menor altura pode encontrar um limite maior; mover a maior mantém o gargalo e pode ser descartado.",
      "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
      "keywords": [
        "área",
        "altura",
        "heights",
        "leftindex",
        "rigthindex",
        "menor",
        "punteros",
        "maxarea",
        "problema",
        "leetcode"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/desenoxidando-logica-container-with-most-water"
      }
    },
    {
      "id": "8636e2f51fc50068",
      "url": "https://imrafaeldev.site/artigos/desenferrujando-logica-group-anagrams",
      "title": "Desenferrujando a lógica #01: Group Anagrams (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Desenferrujando a lógica #01: Group Anagrams\n\n## Desenferrujando a lógica #01: Group Anagrams\n\nEu não tinha parado de escrever código. O que mudou foi terceirizar partes do raciocínio. Group Anagrams foi o exercício para recuperar o hábito.\n\n17 de agosto de 2026\n\n- [Trade-offs](/artigos/?topic=trade-offs)\n- [Performance](/artigos/?topic=performance)\n\nDepois de anos programando, percebi que minha lógica estava enferrujada. Eu não tinha parado de escrever código. O que mudou foi que, aos poucos, comecei a terceirizar partes do raciocínio que antes precisava exercitar sozinho.\n\nEscolhi o exercício [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/) no LeetCode. A proposta é receber uma lista de strings e reunir os anagramas no mesmo grupo.\n\n## O que precisamos resolver\n\nEntrada:\n\n[\"eat\", \"tea\", \"tan\", \"ate\", \"nat\", \"bat\"]\nResultado possível:\n\n[\"eat\", \"tea\", \"ate\"]\n[\"tan\", \"nat\"]\n[\"bat\"]\nA ordem dos grupos não importa.\n\n## O que é um anagrama?\n\nPegue eat, tea e ate. Cada uma tem a uma vez, e uma vez e t uma vez. A posição muda; a quantidade de cada letra continua igual.\n\nO algoritmo precisa transformar essas palavras em uma representação comum. Se as três produzirem a mesma chave, posso usar essa chave em um map e colocá-las no mesmo grupo. O primeiro problema é criar essa chave.\n\n## Minha primeira resposta foi ordenar\n\nComecei usando a string ordenada como chave:\n\neat → aet\ntea → aet\nate → aet\nAs três produzem aet.\n\n### Solução usando sort\n\nEssa foi a primeira solução que me ocorreu. Eu não estava tentando a implementação mais enxuta de imediato. Queria montar uma solução coerente e entender onde ela poderia melhorar.\n\nfunc sortString(str string) string {\nb := []byte(str)\nslices.Sort(b)\nreturn string(b)\n}\n\nfunc groupAnagrams(strs []string) [][]string {\nmapping := make(map[string][]string)\n\nfor _, str := range strs {\nsortedStr := sortString(str)\nmapping[sortedStr] = append(mapping[sortedStr], str)\n}",
      "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
      "keywords": [
        "string",
        "para",
        "chave",
        "cada",
        "group",
        "não",
        "solução",
        "result",
        "problema",
        "sort"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/artigos/desenferrujando-logica-group-anagrams"
      }
    },
    {
      "id": "87c37b7fda1c1313",
      "url": "https://imrafaeldev.site/en/articles",
      "title": "Articles (Part 2)",
      "content": "[Trade-offs](/en/articles/?topic=trade-offs)\n- [Performance](/en/articles/?topic=performance)\nNode.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.\n\n-\n\n## [TypeScript Clean Architecture: Core, Adapters, and Infra](/en/articles/typescript-clean-architecture/)\n\nMarch 15, 2023\n\n[Architecture](/en/articles/?topic=arquitetura)\nWeak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.\n\n-\n\n## [Design Patterns: Strategy](/en/articles/design-patterns-strategy/)\n\nMay 5, 2022\n\n[Architecture](/en/articles/?topic=arquitetura)\nIf/else chains grow and turn fragile. Strategy isolates each algorithm behind a contract.\n\n-\n\n## [Stop being hostage to dependencies. Say hello to the Adapter design pattern](/en/articles/design-patterns-adapter/)\n\nApril 26, 2022\n\n[Architecture](/en/articles/?topic=arquitetura)\nMigrating from MySQL to PostgreSQL does not require rewriting the service. The Adapter isolates the plugin behind a contract the business rule understands.",
      "description": "Engineering patterns and decisions in technical prose, with code.",
      "keywords": [
        "articles",
        "topic",
        "performance",
        "architecture",
        "with",
        "trade-offs",
        "2026",
        "arquitetura",
        "concurrency",
        "2022"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/articles"
      }
    },
    {
      "id": "883151c62b2688a4",
      "url": "https://imrafaeldev.site/es",
      "title": "Rafael Pereira, ingeniero de software sénior (Part 2)",
      "content": "~7 min → <1 s comisiones, de la carga original a la caché caliente\n\n### [VBET: analítica sobre un SQL Server que no podíamos cambiar](/es/casos/analitica-sql-server-externo/)\n\nLa base era de otro equipo. El dashboard necesitaba dejar de depender de un schema que no controlábamos.\n\n[Abrir el case](/es/casos/analitica-sql-server-externo/)\n\nPipeline de comissões\nCada estágio corresponde a uma decisão incremental documentada no case. Números de outras histórias não entram neste desenho.\n\n[Ver o caso](/es/casos/analitica-sql-server-externo/)\n\nFlapper sep/2021 – jun/2022\n\n~25% menos tablas en la separación por dominios\n\n### [Flapper: descubrir el dominio antes de separar el monolito](/es/casos/modernizacion-monolito-sin-documentacion/)\n\nEl producto no podía parar, y los autores originales ya no estaban. Antes de migrar, fue preciso descubrir qué fronteras aún revelaba la base.\n\n[Abrir el case](/es/casos/modernizacion-monolito-sin-documentacion/)\n\nDescoberta de domínio antes da migração\nO banco legado revela fronteiras; pessoas, autenticação e aeronaves saem gradualmente para contextos com persistência própria.\n\n[Ver o caso](/es/casos/modernizacion-monolito-sin-documentacion/)\n\nArtefactos\n\n## Proyectos seleccionados\n\nRepositorio público\n\n### [SMS Manager](/es/proyectos/sms-manager/)\n\nImportar CSV no puede bloquear la API Nest. La campaña persiste y publica en la cola RabbitMQ; consumidores en TypeScript, Go o Rust graban el resultado en Mongo. La API no espera a la operadora.\n\n[Abrir el proyecto](/es/proyectos/sms-manager/)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\nExperimento documentado\n\n### [goc_mcp](/es/proyectos/goc-mcp/)\n\nMaestro (Codex/Cursor) delega vía MCP; daemon FIFO y workers OpenCode ejecutan con estado en SQLite. La orquestación funcionó, pero la medición no confirmó reducción de costo o tiempo.",
      "description": "Portafolio institucional y hub editorial de Rafael Pereira. Trabajo en los puntos en los que los sistemas simples dejan de ser simples.",
      "keywords": [
        "casos",
        "proyectos",
        "abrir",
        "case",
        "qué",
        "caso",
        "fallos",
        "decisión",
        "contacto",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/es"
      }
    },
    {
      "id": "887b09554a8f3c34",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 2)",
      "content": "import (\n\t\"fmt\"\n\t\"runtime\"\n\t\"sync\"\n)\n\nfunc process(item int) int {\n\t// Simula transformação pura de CPU.\n\treturn item * item\n}\n\nfunc main() {\n\titems := []int{1, 2, 3, 4, 5, 6, 7, 8}\n\tresults := make([]int, len(items))\n\n\tnumWorkers := runtime.GOMAXPROCS(0)\n\tjobs := make(chan int)\n\n\tvar wg sync.WaitGroup\n\tfor w := 0; w < numWorkers; w++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor index := range jobs {\n\t\t\t\tresults[index] = process(items[index])\n\t\t\t}\n\t\t}()\n\t}\n\tfor i := range items {\n\t\tjobs <- i\n\t}\n\tclose(jobs)\n\twg.Wait()\n\tfmt.Println(results)\n}\n```\n\n**Quando escolher outra abordagem.** Mantenha o valor default de `GOMAXPROCS`; se considerar sobrescrevê-lo, meça antes e depois em benchmarks controlados. Para tarefas puramente sequenciais, dependentes entre si ou com overhead de coordenação maior que o ganho, o laço simples sem goroutines é mais legível e mais rápido. Para paralelismo de dados em lote com cancelamento e limite de erro, prefira `errgroup` ou um pool com semáforo em vez de disparar goroutines sem controle.\n\n## 2. Ownership e cancelamento com context\n\n**Mecanismo.** `context.Context` propaga cancelamento, deadline e valores de escopo de requisição ao longo de uma cadeia de chamadas. O dono do contexto (normalmente a borda de entrada: handler HTTP, consumidor de fila, função `main`) cria um contexto cancelável ou com timeout; as funções internas apenas observam `<-ctx.Done()` e retornam `ctx.Err()`. Contexto é imutável: `WithCancel`, `WithTimeout` e `WithValue` derivam um filho sem alterar o pai.\n\n**Falha ou limite que ele trata.** Sem ownership claro, goroutines órfãs continuam trabalhando depois que o cliente desistiu, o deploy desligou ou o timeout estourou — desperdiçando CPU, conexões e memória. O limite do mecanismo: contexto não cancela código por força; se a função ignorar `ctx.Done()` ou bloquear em operação sem suporte a contexto, o cancelamento nunca acontece.",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "88cd321a630747f0",
      "url": "https://imrafaeldev.site/experiences/vbet-en",
      "title": "VBET | Rafael Pereira",
      "content": "## Context\n\nAt VBET, I worked on an analytics product for iGaming affiliates and influencers. It calculated financial and operational metrics in a platform that began serving audiences much larger than originally expected.\n\n## How I worked\n\nBefore addressing performance, I reduced security and maintenance risks in the legacy API. I replaced unsafe queries, organized the codebase around Clean Architecture and dependency injection, and established tests and documentation to support the following changes.\n\nI then addressed performance in stages. I used Go, goroutines, channels, and parallel queries to reduce the first commission-calculation stage from about seven to three minutes. Since the SQL Server was external and could not be changed, I designed an ETL with checkpoints, pre-calculated aggregates, and reconciliation, separating provisional data from consolidated data.\n\n## What I took from it\n\nThis experience consolidated how I make performance decisions: understand the actual constraint, accept the consistency appropriate to each use, and then choose the technology that addresses it.",
      "description": "My experience at VBET with security, analytics, and performance at scale.",
      "keywords": [
        "performance",
        "from",
        "worked",
        "that",
        "queries",
        "then",
        "data",
        "consolidated",
        "context",
        "vbet"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-vbet",
        "slug": "vbet",
        "title": "VBET | Rafael Pereira",
        "description": "My experience at VBET with security, analytics, and performance at scale.",
        "company": "VBET",
        "role": "Senior Backend Engineer",
        "period": "Oct 2023 to Feb 2025",
        "caseSlug": "external-sql-server-analytics",
        "caseSummary": "The case study follows the analytics dashboard evolution: from API risk reduction to ETL, pre-computation, reconciliation, and caching over an external SQL Server.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/vbet-en.md"
      }
    },
    {
      "id": "896aedff0dd82ca2",
      "url": "https://imrafaeldev.site/en/articles/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Goroutines vs Event Loop: the wrong comparison between two concurrency models\n\n## Goroutines vs Event Loop: the wrong comparison between two concurrency models\n\nNode.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.\n\nJune 24, 2026\n\n- [Trade-offs](/en/articles/?topic=trade-offs)\n- [Performance](/en/articles/?topic=performance)\n\nThe thesis is simple: Node.js with the Event Loop and Go with goroutines do not solve the same kind of problem the same way. The comparison goes bad when we treat both as direct competitors in every scenario. In practice, the most common mistake is confusing concurrency with parallelism.\n\nConcurrency is organizing several tasks that may be underway at the same time. Parallelism is actually executing work at the same time, using multiple CPU cores. That difference sounds academic until it shows up in production.\n\nThe Node.js Event Loop is very good when the bottleneck is waiting: external APIs, databases, WebSockets, user input, queues, and events. While one operation waits for a response, the loop keeps serving other tasks. It is the corner-store owner at the counter: he does not stop because he asked someone to fetch candy from the stockroom.\n\nGoroutines, on the other hand, start looking more interesting when the work is CPU bound, divisible, and can use multiple cores with explicit concurrency control. They are lightweight execution units managed by the Go runtime. With them, a task can be broken into smaller parts, distributed, and synchronized at the end.\n\nIt is more like a busy stall at the São João festival in Caruaru: one person grills corn, another stirs canjica, another slices bolo de rolo. Work advances at the same time, each person handling one part.\n\n## Where Node.js starts to suffer",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "loop",
        "event",
        "problem",
        "work",
        "goroutines",
        "same",
        "concurrency"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/en/articles/goroutines-vs-event-loop"
      }
    },
    {
      "id": "89b9542eec9cca72",
      "url": "https://imrafaeldev.site/artigos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 4)",
      "content": "Nesses casos, o Event Loop é uma excelente escolha. Ele permite alto volume de operações concorrentes sem criar uma thread por requisição. Para aplicações orientadas a evento, isso é simples, produtivo e fácil de encaixar no ecossistema JavaScript.\n\nO erro é tentar empurrar esse mesmo modelo para um cálculo pesado e achar que concorrência de I/O vira paralelismo de CPU. Não vira.\n\n## Quando eu olharia para Go\n\nEu começaria a olhar para Go quando a tarefa fosse claramente CPU bound: cálculo em alto volume de dados, processamento de imagem em lote, agregações pesadas, compressão, transformação grande de buffers, simulações ou qualquer rotina em que a máquina passa mais tempo calculando do que esperando resposta externa.\n\nNesse tipo de cenário, goroutines com worker pool dão um controle melhor sobre uso de CPU. Também deixam mais explícita a separação entre unidades de trabalho, sincronização e coleta de resultado.\n\nMas Go também cobra preço. É preciso pensar em granularidade dos chunks, consumo de memória, cancelamento, tratamento de erro, backpressure, limites de workers, contenção e consistência do resultado.\n\nSe a divisão do trabalho for ingênua, o ganho de CPU pode vir acompanhado de estouro de memória ou complexidade desnecessária.\n\n## Contraargumento: Node.js também tem worker threads\n\nExiste um contraargumento justo: Node.js não está limitado ao Event Loop para tudo. Worker threads existem justamente para executar trabalho pesado fora do thread principal. Também há estratégias com filas, processos separados, serviços auxiliares e native addons.\n\nEntão a comparação honesta não é “Node.js não consegue”. Consegue.\n\nA questão é custo de implementação, maturidade da equipe, observabilidade, integração com o sistema existente e quanto esforço vale a pena investir para manter aquele processamento dentro do ecossistema Node.",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "loop",
        "mesmo",
        "trabalho",
        "event",
        "problema",
        "mais",
        "tempo"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/artigos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "89f69a75ae0c9acf",
      "url": "https://imrafaeldev.site/en/cases",
      "title": "Case studies",
      "content": "- [Home](/en/)\n- Cases\n\n## Case studies\n\nEach case documents the constraint, the decision, the discarded alternative, and the measured outcome. It also states when to revisit the decision.\n\n- Infosistemas Feb/2025 – May/2026\n\n## [Infosistemas: a failure contract for RabbitMQ messaging](/en/cases/rabbitmq-messaging/)\n\nIntermittent failures between microservices with no contract for retry, DLQ, or duplication. Adding more consumers only pushed the overload elsewhere.\n\n**~98%** fewer intermittent failures in critical flows\n\n[Open the case — Infosistemas: a failure contract for RabbitMQ messaging](/en/cases/rabbitmq-messaging/)\n\n- VBET Oct/2023 – Feb/2025\n\n## [VBET: analytics on top of a SQL Server we could not change](/en/cases/external-sql-server-analytics/)\n\nThe database belonged to another team. The dashboard had to stop depending on a schema we did not control.\n\n**~7 min → <1 s** commissions, from the original load to the warm cache\n\n[Open the case — VBET: analytics on top of a SQL Server we could not change](/en/cases/external-sql-server-analytics/)\n\n- Flapper Sep/2021 – Jun/2022\n\n## [Flapper: discovering the domain before splitting the monolith](/en/cases/undocumented-monolith-modernization/)\n\nThe product could not stop, and the original authors were already gone. Before migrating, we had to discover which boundaries the database still revealed.\n\n**~25%** fewer tables in the domain-based split\n\n[Open the case — Flapper: discovering the domain before splitting the monolith](/en/cases/undocumented-monolith-modernization/)",
      "description": "Each case documents the constraint, the decision, the discarded alternative, and the measured outcome. It also states when to revisit the decision.",
      "keywords": [
        "cases",
        "case",
        "infosistemas",
        "contract",
        "open",
        "vbet",
        "could",
        "flapper",
        "before",
        "2025"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/cases"
      }
    },
    {
      "id": "8ad0a3e4498fda5d",
      "url": "https://imrafaeldev.site/en/experience/flapper",
      "title": "Flapper",
      "content": "- [Home](/en/)\n- Flapper\n\n## Flapper\n\nMy experience at Flapper with incremental modernization of a production legacy system.\n\nRole\n\nFull Stack Software Engineer\n\nPeriod\n\nSep 2021 to Jun 2022\n\n## Context\n\nAt Flapper, I worked on an executive aviation platform whose main product was a PHP monolith more than seven years old, with little useful documentation and no original developers available to explain the system.\n\n## How I worked\n\nThe challenge was not to replace PHP with TypeScript. The application was in production and sustained the business, so I began with domain discovery. I used the database to map relationships, identify bounded contexts, and plan an incremental migration based on the Strangler pattern.\n\nI migrated modules such as people, authentication, and aircraft to Node.js, NestJS, and Go services. To reduce relational dependencies between contexts, we worked with local projections and Kafka events; gRPC, REST, and GraphQL were used according to each integration’s needs.\n\n## What I took from it\n\nThe experience consolidated my view of legacy modernization: technology comes after understanding boundaries, risks, and a sequence that preserves the operation. Alongside module delivery, I documented decisions and led workshops so the team could continue the transformation.\n\n## Related case study\n\nThe case study shows how I investigated the domain from the database and code and led incremental monolith modernization without interrupting the operation.\n\n[Read the full case study](/en/cases/undocumented-monolith-modernization/)",
      "description": "My experience at Flapper with incremental modernization of a production legacy system.",
      "keywords": [
        "with",
        "flapper",
        "incremental",
        "modernization",
        "worked",
        "case",
        "study",
        "experience",
        "production",
        "legacy"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/flapper"
      }
    },
    {
      "id": "8cdb71d66ba19e19",
      "url": "https://imrafaeldev.site/artigos/desenferrujando-logica-container-with-most-water",
      "title": "Desenferrujando a lógica #02: Container With Most Water (Part 3)",
      "content": "O detalhe do avanço importa. Na versão que eu havia escrito, os ponteiros só avançavam quando a área atual não era maior que maxArea. Se uma nova área máxima fosse encontrada, a mesma combinação seria calculada de novo, sem sair do loop. A correção foi separar as duas decisões: registrar a área e, depois, mover o ponteiro da menor altura.\n\n## O resultado das tentativas\n\nO histórico do LeetCode ficou assim:\n\n- JavaScript: aceita, 3 ms e 63.6 MB.\n\n- JavaScript: resposta incorreta.\n\n- JavaScript: resposta incorreta.\n\n- Go: aceita, 0 ms e 9.6 MB.\n\n- TypeScript: aceita, 3 ms e 63.9 MB.\n\n- Go: resposta incorreta.\n\nForam três tentativas com resposta incorreta antes de chegar às soluções aceitas em JavaScript, Go e TypeScript. Mais do que contar submissões, eu queria olhar para o erro, entender a hipótese que falhou e tentar de novo sem terceirizar todo o raciocínio.\n\n## Complexidade\n\nO algoritmo percorre o array uma vez. Em cada iteração, um dos ponteiros avança, então a complexidade de tempo é O(n) e a complexidade de espaço é O(1).\n\nA primeira tentativa também usava dois ponteiros, mas fazia trabalho extra para comparar possibilidades futuras. A segunda solução usa uma propriedade do problema para descartar com segurança parte das combinações.\n\nEsse foi o exercício desta vez: não confundir uma escolha que parece boa agora com uma decisão que o problema realmente permite justificar.\n\n---\n\n*Série Desenferrujando a lógica #02 — Container With Most Water. Problema em [leetcode.com/problems/container-with-most-water](https://leetcode.com/problems/container-with-most-water/).*\n\nGeometria: min da altura × largura\nNas posições 1 e 8, a menor altura é 7 e a largura é 7. O recipiente produz área 49.\n\nDois ponteiros: mova a menor linha\nA largura sempre diminui. Só mover a menor altura pode encontrar um limite maior; mover a maior mantém o gargalo e pode ser descartado.",
      "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
      "keywords": [
        "área",
        "altura",
        "heights",
        "leftindex",
        "rigthindex",
        "largura",
        "menor",
        "ponteiros",
        "para",
        "maior"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/artigos/desenferrujando-logica-container-with-most-water"
      }
    },
    {
      "id": "8d86884fd9ee4714",
      "url": "https://imrafaeldev.site/projetos/diffvision",
      "title": "DiffVision",
      "content": "- [Início](/)\n- [Projetos](/projetos/)\n- DiffVision\n\nCLI pública; revisão por IA ainda mock\n\n## DiffVision\n\nO diff Git abre na UI local; comentários e exportação em Markdown ficam no repositório. A revisão visual por IA permanece mock.\n\n[Repositório](https://github.com/imrafaeldev/diffvision-app)\n\nRevisão local-first\nDiff no disco, UI local, comentários e export em `.diffvision/`. A plataforma remota fica de fora; a revisão por IA visual ainda é mock.\n\n- [01Problema](#problema)\n- [02Restrições](#restricoes)\n- [03Decisão](#decisao)\n- [04Estado atual](#estado-atual)\n- [05Limitações](#limitacoes)\n\n## Problema\n\nRevisar um diff Git em ferramenta SaaS envia código para fora e mistura UI remota com o histórico local. A revisão precisa funcionar offline, com hunks, filtros, bookmarks e comentários ancorados em linhas.\n\n## Restrições\n\nPreferências e relatórios devem viver no próprio repositório (.diffvision/). A CLI npm inicia backend e UI locais. Integração com assistente não pode ser vendida como pronta se ainda for protótipo.\n\n## Decisão\n\nCLI inspeciona o Git, interpreta diff unificado e sobe interface web. Backend Fastify com snapshot e WebSocket; UI React/Vite. Exportação Markdown/JSON no repositório. Pacote diffvision-mcp por stdio para resumir repositório, ler patches e registrar comentários. O assistente visual de revisão por IA é declarado mock/protótipo; a escrita de comentários via MCP é funcional.\n\n## Estado atual\n\nDistribuído como CLI npm, com execução local-first.\n\n## Limitações\n\nNão substitui o fluxo de review do GitHub. O fluxo de IA visual não deve ser lido como produto acabado.",
      "description": "CLI npm local-first para revisar diffs Git, com UI local, comentários no repositório, exportação em Markdown/JSON e servidor MCP. A revisão visual por IA permanece mock.",
      "keywords": [
        "revisão",
        "comentários",
        "repositório",
        "diffvision",
        "mock",
        "diff",
        "visual",
        "ainda",
        "local",
        "não"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/projetos/diffvision"
      }
    },
    {
      "id": "8dd31f85e8903220",
      "url": "https://imrafaeldev.site/en/articles/go-intensive",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Go intensive: concurrency, resilience, and distributed systems in 30 minutes\n\n## Go intensive: concurrency, resilience, and distributed systems in 30 minutes\n\nA 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.\n\nSeptember 22, 2026\n\n- [Architecture](/en/articles/?topic=arquitetura)\n- [Performance](/en/articles/?topic=performance)\n- [Messaging](/en/articles/?topic=mensageria)\n\nThis is a review for backend engineers who already know Go and need to discuss or build production services again. The useful decisions are bounded concurrency, cancellation, finite queues, idempotency, and observability.\n\nThe scope is Go 1.26. It is not a language introduction. Move quickly through syntax and spend time on the choices that shape a consumer, API, or telemetry pipeline.\n\n## 30-minute route\n\nTime\n\nTopic\n\nPriority\n\n0-4 min\n\nTypes, structs, interfaces, errors\n\nQuick review\n\n4-10 min\n\nGoroutines, channels, select, context\n\nHigh\n\n10-17 min\n\nBounded concurrency and backpressure\n\nHighest\n\n17-23 min\n\nResilient IoT pipeline\n\nHighest\n\n23-26 min\n\nRuntime, memory, profiling\n\nHigh\n\n26-30 min\n\nArchitecture and interview answers\n\nHighest\n\n## Production fundamentals\n\nGo favors composition, small contracts, and explicit flow. A value can validate itself without a framework:\n\nvar ErrOutOfRange = errors.New(\"reading out of range\")\n\ntype Reading struct {\nDeviceID string\nSequence uint64\nValue float64\n}",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "reading",
        "context",
        "channel",
        "errors",
        "return",
        "value",
        "device",
        "goroutine",
        "work",
        "articles"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/en/articles/go-intensive"
      }
    },
    {
      "id": "8de19fd581e34e73",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-group-anagrams",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 3)",
      "content": "var key [26]uint8 crea 26 posiciones, una para cada letra de a a z. key[str[i]-'a']++ encuentra la posición de cada carácter e incrementa el contador. Después, groups[key] = append(groups[key], str) usa el propio vector de frecuencias como clave del grupo.\n\nfunc groupAnagrams(strs []string) [][]string {\ngroups := make(map[[26]uint8][]string, len(strs))\n\nfor _, str := range strs {\nvar key [26]uint8\n\nfor i := 0; i &#x3C; len(str); i++ {\nkey[str[i]-'a']++\n}\n\ngroups[key] = append(groups[key], str)\n}\n\nresult := make([][]string, 0, len(groups))\n\nfor _, group := range groups {\nresult = append(result, group)\n}\n\nreturn result\n}\n\n## ¿Qué cambió en complejidad?\n\n- Ordenación: O(k log k) por string y O(n × k log k) para n strings.\n\n- Conteo: O(k) por string y O(n × k) para n strings.\n\nLa mejora apareció cuando percibí que la ordenación hacía un trabajo que el problema no exigía. El cambio principal es de O(k log k) a O(k) por string.\n\n## La primera solución no estaba equivocada\n\nResuelve el problema y, según el contexto, podría bastar. El hábito que quería recuperar era seguir pensando después de que el código empieza a funcionar. Encontrar una solución, volver al problema y preguntar: ¿qué trabajo está haciendo mi algoritmo sin necesitarlo?\n\nNo hago estos ejercicios porque LeetCode represente todo el trabajo de ingeniería de software, ni para disputar la solución más sofisticada. Los hago porque percibí que usar IA todos los días redujo la cantidad de veces en que necesito insistir solo en un problema. Quiero reservar espacio para ejercitarlo de nuevo: leer, intentar, errar, revisar el enfoque y solo después comparar caminos.\n\n---\n\n*Serie Desenoxidando la lógica #01 — Group Anagrams. Adaptado del carrusel autoral; problema en [leetcode.com/problems/group-anagrams](https://leetcode.com/problems/group-anagrams/).*\n\nChave por sort e mapa\nCada string vira chave ordenada (eat → aet). O mapa agrupa anagramas sob a mesma chave sem comparar cada par.",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "group",
        "clave",
        "solución",
        "result",
        "problema",
        "sort",
        "groups"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/es/articulos/desenoxidando-logica-group-anagrams"
      }
    },
    {
      "id": "8e0e67e19db7a71e",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-pt-br",
      "title": "Design Patterns: Strategy (Part 3)",
      "content": "O contexto recebe e executa a estratégia. Ele conhece apenas o contrato, não a implementação concreta. A antiga `DefaultCalculator` passa a receber esse `ContextAnalyzer` por injeção:\n\n```typescript\nclass Calculator {\n  constructor(private readonly contextAnalyzer: ContextAnalyzer) {}\n\n  /**\n   * A implementação de calculate em Calculator não muda a cada operação.\n   * O que cresce é o ContextAnalyzer, que adiciona um case por operação nova.\n   */\n  public calculate(parameters: BinaryOperationParameters): Result {\n    const { operator, firstOperand, secondOperand } = parameters;\n    return this.contextAnalyzer\n      .getInstance(operator)\n      .calculate({ firstOperand, secondOperand });\n  }\n}\n```\n\n## Por que usar o Strategy?\n\nO Strategy ajuda em legado com várias regras de negócio, cada uma representada por um `if` e uma implementação extensa. A parte comum fica no contrato; cada variação de regra fica em sua própria classe; um analisador de contexto (resolver/factory) escolhe a estratégia.\n\nA cada requisição, o código avalia o contexto da operação e seleciona a implementação correspondente ao contrato.\n\n## Relação com outros padrões\n\n- Adapter: o Strategy varia comportamento; o Adapter isola dependências externas atrás de uma interface própria.\n- SOLID (OCP): o Strategy é uma forma de aplicar o Open/Closed Principle.",
      "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "parameters",
        "operator",
        "cada",
        "strategy",
        "operação",
        "calculate"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
        "excerpt": "Cadeias de if/else crescem e ficam frágeis. O Strategy isola cada algoritmo atrás de um contrato.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-pt-br.md"
      }
    },
    {
      "id": "8e1c8d7664264a28",
      "url": "https://imrafaeldev.site/experiencias/azify",
      "title": "Azify",
      "content": "- [Início](/)\n- Azify\n\n## Azify\n\nMinha consultoria na Azify com liquidação, BaaS e serviços financeiros.\n\nCargo\n\nEngenheiro Backend Sênior — Consultoria\n\nPeríodo\n\nmar/2025 a jun/2025\n\n## O contexto\n\nNa Azify, atuei como consultor em infraestrutura financeira para fintechs e pequenos bancos. O produto reunia capacidades como Pix, transferências, cartões, carteira digital e outros serviços bancários.\n\n## Como atuei\n\nParticipei de decisões arquiteturais e ajudei a estruturar práticas de desenvolvimento para um ambiente em que consistência, segurança e estabilidade tinham impacto financeiro direto. Desenvolvi um motor de liquidação em NestJS com integrações a múltiplas exchanges, monitoramento de risco e controles de compliance.\n\nTambém trabalhei na integração de exchanges e blockchains aos fluxos transacionais e na evolução de uma plataforma BaaS multi-tenant com OAuth 2.0, JWT e criptografia. Com profiling de consultas, revisão de índices e ajuste do uso de Redis, reduzi em 30% a latência de APIs financeiras críticas.\n\n## O que levo\n\nEssa consultoria retomou temas recorrentes da minha trajetória, como pagamentos, multi-tenancy e sistemas transacionais, com um grau de responsabilidade ainda maior sobre autorização e consistência.",
      "description": "Minha consultoria na Azify com liquidação, BaaS e serviços financeiros.",
      "keywords": [
        "azify",
        "como",
        "consultoria",
        "2025",
        "minha",
        "liquidação",
        "baas",
        "serviços",
        "atuei",
        "para"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/azify"
      }
    },
    {
      "id": "8e90d631a7354140",
      "url": "https://imrafaeldev.site/es/articulos/backend-performance-cerca-de-los-datos",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 4)",
      "content": "El resultado permanece correcto, pero ahora existe un intervalo que el índice puede recorrer. La confirmación viene de la comparación de las lecturas y del operador de acceso en los dos planes. Si la métrica no cambia, la hipótesis no se sostuvo.\n\n## Lee el plan por la secuencia del trabajo\n\nDespués de comparar las versiones del filtro, leo el plan como una historia del trabajo producido por la consulta. Empiezo por el tiempo total y por las lecturas. Si la pantalla devuelve diez indicadores, pero la ejecución hace cientos de miles de lecturas, hay una cuenta por explicar.\n\nDespués comparo la cardinalidad estimada con la real. Cuando el optimizador esperaba pocas filas y recibió muchas, puede haber elegido joins, memoria y agregaciones para un escenario bien menor que el encontrado durante la ejecución.\n\nEnseguida acompaño dónde crecen los datos. Busco el operador en que un JOIN lleva miles de filas a cientos de miles, poco antes de que un filtro o GROUP BY lo reduzca todo de nuevo. La respuesta final pequeña puede esconder un volumen intermedio enorme. Es en ese camino donde agregaciones y ordenaciones empiezan a gastar demasiada CPU.\n\nTampoco tomo el porcentaje de costo exhibido por el plan como sentencia. Sirve para elegir dónde medir. Marco el operador caro, cuántas filas entran y salen de él y cuánto tiempo consume.\n\nSi el costo aparece después de la multiplicación de las filas y antes de los pocos indicadores necesarios, ya existe una hipótesis concreta para probar.\n\n## Romper la consulta donde crece el costo\n\nLo que resolvió el dashboard fue romper la consulta y aprovechar los índices correctamente. La división no ocurrió de forma arbitraria. El propio plan mostró dónde el costo empezaba a crecer.\n\nMantuvimos en la base los filtros y la búsqueda indexada de los registros necesarios. Llevar ese recorte a la aplicación haría que el servidor recibiera un conjunto mayor antes de poder descartarlo. También desperdiciaría el camino que los índices ya acortaban.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "base",
        "consulta",
        "para",
        "plan",
        "puede",
        "antes",
        "ejecución",
        "datos",
        "trabajo",
        "cuando"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/backend-performance-cerca-de-los-datos"
      }
    },
    {
      "id": "8f3d9d074996d215",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 7)",
      "content": "@Post()\n  async createUser(@Body() user: UserDto): Promise<UserEntity> {\n    return this.service.createUser(user.name);\n  }\n\n  @Put(\":id\")\n  async updateUser(\n    @Param(\"id\") id: string,\n    @Body() user: UserDto,\n  ): Promise<UserEntity> {\n    return this.service.updateUser(id, user.name);\n  }\n\n  @Delete(\":id\")\n  async deleteUser(@Param(\"id\") id: string): Promise<void> {\n    return this.service.deleteUser(id);\n  }\n}\n```\n\nDTO:\n\n```typescript\n// infra/dtos/UserDto.ts\nexport class UserDto implements UserEntityProps {\n  id?: string;\n  name: string;\n\n  constructor(props: UserEntityProps) {\n    this.id = props.id;\n    this.name = props.name;\n  }\n}\n```\n\nDI module:\n\n```typescript\n// infra/modules/UserModule.ts\n@Module({\n  imports: [],\n  controllers: [UserController],\n  providers: [\n    UserService,\n    { provide: CreateUser, useClass: CreateUserUsecase },\n    { provide: UpdateUser, useClass: UpdateUserUsecase },\n    { provide: DeleteUser, useClass: DeleteUserUsecase },\n    { provide: GetUser, useClass: GetUserUsecase },\n    { provide: CreateUserProtocol, useClass: UserPrismaRepository },\n    { provide: UpdateUserProtocol, useClass: UserPrismaRepository },\n    { provide: DeleteUserProtocol, useClass: UserPrismaRepository },\n    { provide: GetUserByNameProtocol, useClass: UserPrismaRepository },\n    { provide: GetUserByIdProtocol, useClass: UserPrismaRepository },\n  ],\n})\nexport class UserModule {}\n```\n\nInfra is freer: it depends on tooling and exists to sustain Core.\n\n## Execution flow\n\nController (Infra) → Service (Adapter) → Usecase (Core) → Protocol (contract) → Repository (Adapter) → database (Infra). The usecase does not import the concrete repository; the service does not import the concrete usecase class. The dependency arrow points inward.\n\n## Complementary CRUD\n\nBeyond registration:\n\n### Fetch\n\n```typescript\n// core/features/GetUser.ts\nexport abstract class GetUser {\n  abstract execute(id: string): Promise<UserEntity>;\n}",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "8faf3700b19152cb",
      "url": "https://imrafaeldev.site/es/articulos/design-patterns-adapter",
      "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter (Part 3)",
      "content": "En el artículo [Design Patterns: Strategy](/es/articulos/design-patterns-strategy/), el foco es intercambiar algoritmos tras un contrato. El Adapter aísla dependencias externas tras una interfaz propia. Ambos se complementan: Strategy varía comportamiento; Adapter traduce el mundo exterior.\n\n---\n\n*Publicado originalmente en [LinkedIn](https://www.linkedin.com/pulse/pare-de-ser-ref%C3%A9m-das-depend%C3%AAncias-diga-bem-vindo-ao-design-rafael/) el 26 de abril de 2022.*\n\nAdapter: protocolo e implementações\nCustomerService usa CreateDatabaseCustomerProtocol. MysqlAdapter e PostgresqlAdapter implementam o contrato e traduzem para cada banco.\n\nCamadas: antes e depois\nAcoplamento direto ao MySQL dificulta migração. Com protocolo e adapter, troca-se o plugin sem reescrever o serviço.",
      "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
      "keywords": [
        "adapter",
        "regla",
        "negocio",
        "servicio",
        "contrato",
        "mysql",
        "plugin",
        "base",
        "createdatabasecustomerprotocol",
        "readonly"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/design-patterns-adapter"
      }
    },
    {
      "id": "9023193d710b3a0c",
      "url": "https://imrafaeldev.site/es/articulos/design-patterns-adapter",
      "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter (Part 2)",
      "content": "interface SuccessfulEntityCreation {\nreadonly id: number;\nreadonly name: string;\nreadonly email: string;\nreadonly birthDate: Date;\n}\n\ninterface CreateDatabaseCustomerProtocol {\ncreateCustomerOnDatabase(\ncustomer: CustomerInputEntity,\n): SuccessfulEntityCreation;\n}\nEn lugar de **Controller → Service → Database**, pasamos a **Controller → Service → Protocols → Plugin**.\n\nEl servicio pierde el conocimiento de cómo el CRUD llega a la base. Está compuesto por protocolos; la implementación concreta entra en tiempo de ejecución — por inyección de dependencias. Mientras usamos PostgreSQL, implementamos los protocolos en los adapters (o connectors).\n\nCreateDatabaseCustomerProtocol puede ser implementado por CreateDatabaseCustomerPostgresqlAdapter, CreateDatabaseCustomerMysqlAdapter, CreateDatabaseCustomerMongoDBAdapter o CreateDatabaseCustomerMockedAdapter. El servicio queda así:\n\nclass CustomerService {\nconstructor(\nprivate readonly createCustomer: CreateDatabaseCustomerProtocol,\n) {}\n\npublic register(customer: CustomerInputEntity): SuccessfulEntityCreation {\nreturn this.createCustomer.createCustomerOnDatabase(customer);\n}\n}\nPara el servicio, da igual si la base devuelve JSON, XML u otro formato — el adapter traduce al contrato que la regla de negocio espera.\n\n## Ventajas\n\n- **Mantenimiento:** cualquier plugin puede sustituirse sin reescribir el servicio.\n\n- **Pruebas:** para probar solo la regla de negocio, inyecta un adapter mock que implemente el mismo protocolo.\n\n- **Código limpio:** responsabilidades separadas; la regla de negocio no carga detalles del driver.\n\n## Próximo paso\n\nToma un frontend con decenas de bibliotecas e identifica lo que realmente usas. Elige una funcionalidad — convertir reales a dólares, por ejemplo. Describe el contrato (entrada y salida) e implementa un adapter sobre la biblioteca que hoy lo hace. Repite donde la dependencia moleste.\n\n## Relación con otros patrones",
      "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
      "keywords": [
        "adapter",
        "regla",
        "negocio",
        "servicio",
        "contrato",
        "mysql",
        "plugin",
        "base",
        "createdatabasecustomerprotocol",
        "readonly"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/design-patterns-adapter"
      }
    },
    {
      "id": "90330a703a19975a",
      "url": "https://imrafaeldev.site/cases/infosistemas-mensageria-pt-br",
      "title": "Infosistemas: contrato de falha na mensageria RabbitMQ (Part 2)",
      "content": "A redução registrada nas falhas intermitentes dos fluxos críticos entre microsserviços foi de aproximadamente 98%. O número descreve esses fluxos após o redesenho, não a operação inteira da empresa nem outras frentes.\n\n## Limitações\n\nMétricas de outras frentes ainda pendentes de método ou confirmação ficam de fora. Se a volumetria ou o mapa de microsserviços mudar de forma que DLQ e prefetch deixem de isolar a falha, o tuning precisa ser revisto com telemetria de fila e de consumidores.",
      "description": "Redesenho da mensageria RabbitMQ na Infosistemas com filas duráveis, DLQ, retry, idempotência e prefetch. O trabalho reduziu em cerca de 98% as falhas intermitentes entre microsserviços.",
      "keywords": [
        "para",
        "falha",
        "não",
        "prefetch",
        "fluxos",
        "outras",
        "frentes",
        "críticos",
        "microsserviços",
        "operação"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "infosistemas-mensageria",
        "slug": "mensageria-rabbitmq",
        "title": "Infosistemas: contrato de falha na mensageria RabbitMQ",
        "description": "Redesenho da mensageria RabbitMQ na Infosistemas com filas duráveis, DLQ, retry, idempotência e prefetch. O trabalho reduziu em cerca de 98% as falhas intermitentes entre microsserviços.",
        "company": "Infosistemas",
        "role": "Engenheiro de Software Sênior / Arquiteto de Software",
        "period": "fev/2025 a mai/2026",
        "excerpt": "Falhas intermitentes entre microsserviços sem contrato para retry, DLQ ou duplicidade. Mais consumidores só empurravam a sobrecarga.",
        "proofValue": "~98%",
        "proofLabel": "redução de falhas intermitentes nos fluxos críticos",
        "featuredClaimId": "infosistemas-rabbitmq-reliability",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/infosistemas-mensageria-pt-br.md"
      }
    },
    {
      "id": "90711fa9b20b63f8",
      "url": "https://imrafaeldev.site/en/resume",
      "title": "Resume (Part 2)",
      "content": "- Mar 2025 to Jun 2025 Remote Azify Senior Backend Engineer — Consulting Expand experience Collapse experience\nBuilt a settlement engine with NestJS and reduced critical financial API latency by 30% through profiling and optimization.\n\nContext\n\nWorked in fintech and crypto assets, on financial services where consistency, security, and stability had direct operational impact.\n\nContribution\n\nBuilt a NestJS settlement engine with financial integrations and reduced critical API latency by 30% through profiling and optimization.\n\n[Read the full experience](/en/experience/azify/)\n\n- Oct 2023 to Feb 2025 Remote VBET Senior Backend Engineer Expand experience Collapse experience\nUsed Go, goroutines, and channels to reduce the first commission-calculation step from about seven to three minutes, while also contributing to the React application.\n\nContext\n\nThe analytics product served iGaming influencers and affiliates; its dashboard combined commission metrics and allowed a small lag for same-day data, while payouts required consolidated data.\n\nContribution\n\nRestructured the API and calculation path with Go, goroutines, and channels, introduced ETL, pre-computation, and caching, and collaborated on the React application. The measured line went from about seven minutes at peak to about three minutes, about one minute, approximately 15 seconds without cache, and under one second with a warm cache, each belonging to its stage.\n\n[Read the full experience](/en/experience/vbet/)\n\n- Apr 2023 to Oct 2023 Remote Maxmilhas Full Stack Software Engineer Expand experience Collapse experience\nBuilt Node.js and NestJS microservices for cancellation and rebooking automation, reducing manual support intervention by 34%.\n\nContext\n\nWorked on travel post-sale flows, including cancellations, rebooking, coupons, and customer communication.\n\nContribution",
      "description": "Rafael Pereira",
      "keywords": [
        "experience",
        "with",
        "nestjs",
        "full",
        "engineer",
        "node",
        "backend",
        "remote",
        "expand",
        "collapse"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/en/resume"
      }
    },
    {
      "id": "90d84a315295d15f",
      "url": "https://imrafaeldev.site/en/articles/derusting-logic-container-with-most-water",
      "title": "Derusting logic #02: Container With Most Water (Part 2)",
      "content": "if (paddingLeftArea > paddingRightArea &#x26;&#x26; paddingLeftArea > maxArea) {\nleftIndex += 1;\n} else {\nrigthIndex -= 1;\n}\nThe problem was in the hypothesis. The best local decision does not guarantee the best area over the rest of the array. I tried to guess the path by looking only at the next two moves. There was also an error in the distance formula of that draft: for a pair of positions, the width is right - left.\n\n## The observation that unlocks the problem\n\nThe area depends on two things: width and shorter height.\n\nWhen the pointers are at positions left and right, moving the pointer of the taller line cannot raise the container’s minimum height. The width always shrinks, and the height limiting the area is still there.\n\nSo the pointer that must advance is the one at the shorter height. It is the only move that can find a taller line and make up for the lost width.\n\nIf the heights are equal, either one can advance. In the code, I chose to advance the left one when heights[leftIndex] &#x3C;= heights[rigthIndex].\n\n## Two-pointer solution\n\n/**\n* @param {number[]} heights\n* @return {number}\n*/\nvar maxArea = function (heights) {\nlet leftIndex = 0;\nlet rigthIndex = heights.length - 1;\nlet maxArea = 0;\n\nwhile (leftIndex &#x3C; rigthIndex) {\nconst minH = Math.min(heights[leftIndex], heights[rigthIndex]);\nconst currentArea = minH * (rigthIndex - leftIndex);\n\nmaxArea = Math.max(maxArea, currentArea);\n\nif (heights[leftIndex] &#x3C;= heights[rigthIndex]) {\nleftIndex++;\n} else {\nrigthIndex--;\n}\n}\n\nreturn maxArea;\n};\nEach round, I compute the current pair’s area, update the largest area found, and move one of the pointers. The while loop converges toward the center and ends.",
      "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
      "keywords": [
        "heights",
        "area",
        "leftindex",
        "rigthindex",
        "that",
        "height",
        "container",
        "with",
        "maxarea",
        "problem"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/en/articles/derusting-logic-container-with-most-water"
      }
    },
    {
      "id": "90ec299c99e78edd",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 6)",
      "content": "Con 50 ítems y 40 ms por llamada, el loop puede añadir cerca de dos segundos a la ruta. Cada `await` inicia la próxima llamada solo después de la respuesta anterior. Los 40 ms parecen pequeños vistos solos. El tiempo total muestra las cincuenta esperas en fila.\n\nCuando el profiling apunta ese camino, toco el código y el diseño del flujo. Separo las operaciones que dependen de la respuesta anterior de aquellas que pueden correr juntas. Para las independientes, pruebo concurrencia limitada.\n\nDisparar cincuenta peticiones a la vez también puede solo desplazar la presión hacia la integración. El cambio debe validarse por el tiempo total de la ruta, por la tasa de errores y por la saturación del servicio remoto. Mejorar una medida mientras otra explota no resuelve el problema.\n\n## Dos horas para localizar la capa, un día para probar\n\n\"Encontramos el cuello de botella\" suele volverse \"resolvimos la lentitud\" demasiado pronto. Después de localizar una consulta mala o un tramo secuencial, aún faltan el cambio, el deploy y la confirmación en producción. Encontrar la capa solo acorta la lista de sospechosos.\n\nEn las primeras dos horas, quiero localizar dónde profundizar: base, aplicación, integración o infraestructura. Abro el plan, comparo tiempo y lecturas, cuento consultas y cronometro el tramo ejecutado después de la base.\n\nSi el plan está saludable y cincuenta llamadas de 40 ms suman cerca de dos segundos, dejo de buscar índice y mido el código. Si las lecturas explotan antes de la agregación, el código espera mientras pruebo la consulta.\n\nEse plazo es una regla de investigación, no una garantía. En el dashboard lento, pasé cerca de tres horas en la terminal mirando el plan, comparando métricas y probando la ruptura de la consulta. El resto del día involucró reunión, acceso, deploy y confirmación en producción.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "923f15bce6381ace",
      "url": "https://imrafaeldev.site/en/cases/rabbitmq-messaging",
      "title": "Infosistemas: a failure contract for RabbitMQ messaging (Part 1)",
      "content": "- [Home](/en/)\n- [Cases](/en/cases/)\n- Infosistemas: a failure contract for RabbitMQ messaging\n\nInfosistemas\n\n## Infosistemas: a failure contract for RabbitMQ messaging\n\nIntermittent failures between microservices with no contract for retry, DLQ, or duplication. Adding more consumers only pushed the overload elsewhere.\n\nSenior Software Engineer / Software Architect Feb/2025 – May/2026\n\n~98% fewer intermittent failures in critical flows\n\nContrato de falha na mensageria\nPublicação confirmada, consumo com prefetch controlado, retry com backoff e DLQ por fluxo. Escalar só consumidores fica de fora do desenho.\n\n- [01Context](#context)\n- [02Constraints](#constraints)\n- [03Problem](#problem)\n- [04Decision](#decision)\n- [05Discarded alternative](#discarded-alternative)\n- [06Implementation](#implementation)\n- [07Result](#result)\n- [08Limitations](#limitations)\n\n## Context\n\nInfosistemas operates management platforms for rental companies, fleets, and automakers. The work happened in the architecture team, in collaboration with DevOps, SREs, and DBAs, between February 2025 and May 2026.\n\nThis case covers the messaging track. Other tracks from the same experience (ERP security, signing journeys, fiscal integrations) exist in the sources but are not included here as extra numbers or claims.\n\n## Constraints\n\nCritical flows crossed microservices. Failure was intermittent: the same operation could complete on one run and fail on the next. Raising concurrency or prefetch without criteria transferred overload to consumers, services, or downstream databases.\n\n## Problem\n\nMessages stopped completing the expected flow. Investigating partial failure was hard. There was no explicit contract for temporary failure, permanent failure, duplication, or poison messages.\n\n## Decision\n\nMessaging was redesigned to make failure behavior predictable:\n\n- durable queues;\n\n- per-flow DLQ, so a message that must neither disappear nor repeat without control has a place to go;",
      "description": "RabbitMQ messaging redesign at Infosistemas with durable queues, DLQ, retry, idempotency, and prefetch. The work reduced intermittent failures between microservices by about 98%.",
      "keywords": [
        "failure",
        "with",
        "prefetch",
        "contract",
        "flows",
        "imrafaeldev",
        "infosistemas",
        "messaging",
        "intermittent",
        "retry"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/cases/rabbitmq-messaging"
      }
    },
    {
      "id": "925d76598b20dd50",
      "url": "https://imrafaeldev.site/es/experiencias/eds-policia-civil-rio",
      "title": "EDS y Policía Civil de Río de Janeiro",
      "content": "- [Inicio](/es/)\n- EDS (Policía Civil de Río de Janeiro)\n\n## EDS (Policía Civil de Río de Janeiro)\n\nMi experiencia de consultoría en sistemas públicos sensibles.\n\nCargo\n\nIngeniero Backend — Consultoría\n\nPeríodo\n\njul/2025 a dic/2025\n\n## Contexto\n\nEn mi consultoría para EDS, trabajé en sistemas destinados a la Policía Civil de Río de Janeiro. El contexto incluía una operación pública crítica, un sistema de gestión de salud de alto volumen y un ERP jurídico en evolución.\n\n## Cómo trabajé\n\nEstructuré el backend del sistema de salud con NestJS y SQL Server. También refactoricé rutas legadas y participé en flujos de automatización de procesos, gestión documental y recolección de evidencias. Seguridad, control de acceso, trazabilidad y cumplimiento de LGPD orientaban cómo debía evolucionar cada ruta.\n\nAdemás del backend, colaboré en componentes compartidos del design system para alinear contratos de API con las interfaces usadas en la operación.\n\n## Lo que me llevé\n\nEl trabajo reforzó el cuidado necesario para evolucionar sistemas sensibles sin perder auditabilidad. En lugar de separar seguridad y entrega, traté acceso y trazabilidad como parte del contrato del producto.",
      "description": "Mi experiencia de consultoría en sistemas públicos sensibles.",
      "keywords": [
        "policía",
        "civil",
        "río",
        "janeiro",
        "consultoría",
        "sistemas",
        "backend",
        "para",
        "2025",
        "sensibles"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/eds-policia-civil-rio"
      }
    },
    {
      "id": "931b6330853c0607",
      "url": "https://imrafaeldev.site/casos/modernizacao-monolito-sem-documentacao",
      "title": "Flapper: descobrir o domínio antes de separar o monólito (Part 2)",
      "content": "- propagar mudanças por eventos em Kafka, em vez de conectar todos os serviços ao banco antigo;\n\n- usar Node.js, NestJS e Go nos primeiros módulos, com gRPC, REST ou GraphQL conforme o consumidor;\n\n- documentar a estratégia e os primeiros módulos para que o time pudesse continuar a transformação.\n\n## Alternativa descartada\n\nReescrever o monólito inteiro, ou manter serviços novos presos ao mesmo banco e às mesmas relações compartilhadas. A primeira opção pararia o negócio; a segunda preservaria o acoplamento que a migração precisava reduzir.\n\n## Resultado\n\nA separação por domínios reduziu em aproximadamente 25% o número de tabelas e criou sete bancos organizados por contexto. Os módulos de pessoas, autenticação e aeronaves foram os primeiros passos de uma transformação planejada para continuar além da entrega inicial.\n\n## Limitações\n\nO número mede a redução de tabelas nessa separação de domínios, não um ganho financeiro, a migração completa ou um resultado de mercado da empresa. A data final da experiência tem divergência histórica em fontes antigas; o período publicado segue o perfil exportado. Se um domínio ainda depender de regras não mapeadas no monólito, sua extração precisa ser adiada ou receber uma integração de transição explícita.\n\nContato\n\n## Tem um sistema que deixou de ser simples?\n\nChame direto pelo @imrafaeldev, sem formulário — para conversa profissional, comece pelo LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Abrir a página de contato](/contato/)",
      "description": "Modernização incremental de um monólito PHP sem documentação na Flapper: banco como fonte de descoberta, bounded contexts e Strangler. A separação por domínios reduziu em aproximadamente 25% o número de tabelas.",
      "keywords": [
        "não",
        "domínio",
        "monólito",
        "banco",
        "para",
        "antes",
        "migração",
        "imrafaeldev",
        "flapper",
        "tabelas"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/casos/modernizacao-monolito-sem-documentacao"
      }
    },
    {
      "id": "932f8d344e5cd8f8",
      "url": "https://imrafaeldev.site/articles/derusting-logic-container-with-most-water-en",
      "title": "Derusting logic #02: Container With Most Water (Part 3)",
      "content": "There were three wrong-answer attempts before reaching the accepted solutions in JavaScript, Go, and TypeScript. More than counting submissions, I wanted to look at the error, understand the hypothesis that failed, and try again without outsourcing all the reasoning.\n\n## Complexity\n\nThe algorithm walks the array once. On each iteration, one of the pointers advances, so time complexity is `O(n)` and space complexity is `O(1)`.\n\nThe first attempt also used two pointers but did extra work comparing future possibilities. The second solution uses a property of the problem to safely discard part of the combinations.\n\nThat was the exercise this time: not mistaking a choice that looks good now for a decision the problem actually lets you justify.\n\n---\n\n*Derusting logic series #02 — Container With Most Water. Problem at [leetcode.com/problems/container-with-most-water](https://leetcode.com/problems/container-with-most-water/).*",
      "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
      "keywords": [
        "heights",
        "area",
        "leftindex",
        "rigthindex",
        "that",
        "height",
        "maxarea",
        "left",
        "pointers",
        "javascript"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "derusting-logic-container-with-most-water",
        "title": "Derusting logic #02: Container With Most Water",
        "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
        "excerpt": "I tried to pick the next step by looking only at the neighbors. It worked in some cases, but the problem asked for a wider view.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/derusting-logic-container-with-most-water-en.md"
      }
    },
    {
      "id": "9402dcb3880ec50a",
      "url": "https://imrafaeldev.site/artigos/intensivao-go",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 2)",
      "content": "func (r Reading) Validate() error {\nif r.DeviceID == \"\" {\nreturn errors.New(\"device_id is required\")\n}\nif r.Value &#x3C; -100 || r.Value > 250 {\nreturn fmt.Errorf(\"%w: %.2f\", ErrOutOfRange, r.Value)\n}\nreturn nil\n}\nAlguns detalhes evitam erros silenciosos:\n\n- O zero value costuma ser utilizável. Prefira tipos cujo estado inicial seja válido quando isso não esconder uma regra de negócio.\n\n- Slice é uma visão sobre um array. Cópias podem compartilhar o mesmo backing array; append pode reutilizá-lo ou alocar outro.\n\n- map não tem ordem de iteração e não suporta leitura e escrita concorrentes sem sincronização.\n\n- string contém bytes imutáveis, normalmente UTF-8. len conta bytes; range decodifica runes.\n\n- defer executa em LIFO, mas avalia os argumentos quando é registrado.\n\nInterfaces são satisfeitas implicitamente. Defina a interface pequena no pacote que a consome, em vez de exportar um contrato grande ao lado da implementação. Erros são valores: acrescente contexto com %w e inspecione a causa com errors.Is ou errors.As.\n\nif err := reading.Validate(); err != nil {\nif errors.Is(err, ErrOutOfRange) {\nreturn sendToDLQ(reading, err)\n}\nreturn fmt.Errorf(\"validate reading: %w\", err)\n}\npanic fica para invariantes quebradas ou falha irrecuperável de inicialização. recover só alcança um panic na mesma goroutine e pertence a fronteiras controladas, como middleware.\n\n## Goroutines, channels e cancelamento\n\nUma goroutine não é uma thread dedicada. O runtime a agenda sobre threads do sistema operacional. Iniciar uma goroutine sem saber quem a cancela ou espera cria risco de leak.\n\nChannels transportam trabalho ou propriedade. Um mutex protege estado compartilhado. Um buffer apenas absorve uma diferença temporária de velocidade; não cria capacidade infinita.\n\njobs := make(chan Reading, 128)\n\ngo func() {\ndefer close(jobs) // quem produz fecha\nfor _, reading := range batch {\njobs &#x3C;- reading\n}\n}()",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "reading",
        "não",
        "para",
        "return",
        "cancelamento",
        "value",
        "quando",
        "context",
        "concorrência",
        "errors"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-go"
      }
    },
    {
      "id": "94f284682e8fdd46",
      "url": "https://imrafaeldev.site/artigos/typescript-cleanarch",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 4)",
      "content": "- Chamados pelo Core — implementam pelo menos um protocol.\n\n- Chamados pela Infra — em geral **services**.\n\n### Connectors, handlers e repositories\n\nClasses que implementam protocols. Cada uma adapta **um** dispositivo externo (ORM, cliente HTTP, fila, filesystem).\n\nConvenção de nomes:\n\n- **Repositories** — protocol ligado a banco (vocabulário familiar).\n\n- **Connectors** — retornam dados sem ser “tabela” (ex.: ClientHttpFetchConnector, ClientHttpAxiosConnector).\n\n- **Handlers** — processam sem retorno síncrono (ex.: publicar em Kafka).\n\nOutros nomes são válidos; o critério é um adapter por dispositivo.\n\nNo CRUD, só repository (mock):\n\n// adapters/repositories/UsersMockRepository.ts\nexport class UsersMockRepository\nimplements\nGetUserByIdProtocol,\nGetUserByNameProtocol,\nCreateUserProtocol,\nUpdateUserProtocol,\nDeleteUserProtocol\n{\nprivate db: DbConnector;\n\nconstructor() {\nthis.db = mockDbConnector;\n}\n\nasync getById(id: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.getById(id);\n}\n\nasync getByName(name: string): Promise&#x3C;UserEntity | null> {\nreturn this.db.users.getByName(name);\n}\n\nasync register(name: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.register(name);\n}\n\nasync update(id: string, name: string): Promise&#x3C;UserEntity> {\nreturn this.db.users.update(id, name);\n}\n\nasync delete(id: string): Promise&#x3C;void> {\nreturn this.db.users.delete(id);\n}\n}\nMock do conector:",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "name",
        "string",
        "core",
        "promise",
        "userentity",
        "async",
        "para",
        "this",
        "regra",
        "export"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/artigos/typescript-cleanarch"
      }
    },
    {
      "id": "954439960ea028fe",
      "url": "https://imrafaeldev.site/es/casos/modernizacion-monolito-sin-documentacion",
      "title": "Flapper: descubrir el dominio antes de separar el monolito (Part 1)",
      "content": "- [Inicio](/es/)\n- [Casos](/es/casos/)\n- Flapper: descubrir el dominio antes de separar el monolito\n\nFlapper\n\n## Flapper: descubrir el dominio antes de separar el monolito\n\nEl producto no podía parar, y los autores originales ya no estaban. Antes de migrar, fue preciso descubrir qué fronteras aún revelaba la base.\n\nIngeniero de Software Full Stack sep/2021 – jun/2022\n\n~25% menos tablas en la separación por dominios\n\nDescoberta de domínio antes da migração\nO banco legado revela fronteiras; pessoas, autenticação e aeronaves saem gradualmente para contextos com persistência própria.\n\n- [01Contexto](#contexto)\n- [02Restricciones](#restricciones)\n- [03Problema](#problema)\n- [04Decisión](#decision)\n- [05Alternativa descartada](#alternativa-descartada)\n- [06Resultado](#resultado)\n- [07Limitaciones](#limitaciones)\n\n## Contexto\n\nEn Flapper, el producto principal era un monolito PHP con más de siete años, poca documentación útil y sin los desarrolladores que lo habían creado. La aplicación sostenía la operación de aviación ejecutiva y no podía interrumpirse para una reescritura.\n\n## Restricciones\n\nEl código y la base acumulaban reglas y dependencias difíciles de explicar. Los cambios tenían efectos colaterales poco predecibles, y no había especialistas remanentes para confirmar cómo cada parte del sistema debía evolucionar. La migración necesitaba coexistir con el producto en producción.\n\n## Problema\n\nCambiar PHP por otra tecnología no respondería la duda principal: qué reglas pertenecían juntas y qué dependencias podían separarse sin romper la operación. El sistema necesitaba fronteras de dominio antes de servicios nuevos.\n\n## Decisión\n\nUsé la base y el código existente como fuente de descubrimiento. Agrupamientos de tablas y relaciones ayudaron a identificar bounded contexts; a partir de ellos, la migración siguió el patrón Strangler:\n\n- extraer un dominio por vez, sin interrumpir el monolito;",
      "description": "Modernización incremental de un monolito PHP sin documentación en Flapper: base como fuente de descubrimiento, bounded contexts y Strangler. La separación por dominios redujo en aproximadamente el 25% el número de tablas.",
      "keywords": [
        "monolito",
        "para",
        "dominio",
        "antes",
        "base",
        "imrafaeldev",
        "flapper",
        "tablas",
        "contexto",
        "migración"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/es/casos/modernizacion-monolito-sin-documentacion"
      }
    },
    {
      "id": "95552bfbb3db69e5",
      "url": "https://imrafaeldev.site/projetos/goc-mcp",
      "title": "goc_mcp",
      "content": "- [Início](/)\n- [Projetos](/projetos/)\n- goc_mcp\n\nExperimento documentado\n\n## goc_mcp\n\nMaestro (Codex/Cursor) delega via MCP; daemon FIFO e workers OpenCode executam com estado em SQLite. A orquestração funcionou, mas a medição não confirmou redução de custo ou tempo.\n\n[Repositório](https://github.com/imrafaeldev/goc_mcp)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\n- [01Problema](#problema)\n- [02Restrições](#restricoes)\n- [03Decisão](#decisao)\n- [04Estado atual](#estado-atual)\n- [05Limitações](#limitacoes)\n\n## Problema\n\nUm maestro (Codex ou Cursor) precisa delegar trabalho a workers OpenCode com ciclo de vida explícito: planejar, iniciar, acompanhar, responder, cancelar e recuperar, sem recursão infinita de agentes.\n\n## Restrições\n\nTudo é local. O daemon autentica em loopback. Há limite global e por workspace. Tarefas interrompidas precisam ser reconciliadas. Corrupção de estado não pode virar escrita cega. No caminho feliz: um worker OpenCode por vez, sem decomposição automática que deixe o maestro sem controle.\n\n## Decisão\n\nImplementação em Go: gateways MCP por stdio, daemon único, máquina de estados e executor FIFO. Persistência em SQLite com WAL; artefatos em JSONL. Adaptador OpenCode com servidor como caminho principal e CLI como fallback. Isolamento contra delegação recursiva e diagnóstico somente leitura se o estado corromper.\n\n## Estado atual\n\nRepositório com testes, ADRs e benchmarks. A orquestração funcionou. As medições publicadas no próprio projeto não confirmaram a hipótese de reduzir tempo e custo.\n\n## Limitações\n\nÉ um experimento. Não afirma ganho de produtividade de engenharia de agentes em produção. O resultado negativo da hipótese faz parte do artefato.",
      "description": "Orquestração local de agentes via MCP em Go: maestro, daemon FIFO, workers OpenCode e SQLite. A entrega funcionou, mas a hipótese de custo e tempo não foi confirmada nesta medição.",
      "keywords": [
        "opencode",
        "estado",
        "não",
        "maestro",
        "daemon",
        "fifo",
        "sqlite",
        "orquestração",
        "hipótese",
        "projetos"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/projetos/goc-mcp"
      }
    },
    {
      "id": "955e8569f1d40a24",
      "url": "https://imrafaeldev.site/en/articles/design-patterns-adapter",
      "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern (Part 2)",
      "content": "interface CreateDatabaseCustomerProtocol {\ncreateCustomerOnDatabase(\ncustomer: CustomerInputEntity,\n): SuccessfulEntityCreation;\n}\nInstead of **Controller → Service → Database**, we move to **Controller → Service → Protocols → Plugin**.\n\nThe service loses knowledge of how the CRUD reaches the database. It is composed of protocols; the concrete implementation arrives at runtime — via dependency injection. While we use PostgreSQL, we implement the protocols in adapters (or connectors).\n\nCreateDatabaseCustomerProtocol can be implemented by CreateDatabaseCustomerPostgresqlAdapter, CreateDatabaseCustomerMysqlAdapter, CreateDatabaseCustomerMongoDBAdapter, or CreateDatabaseCustomerMockedAdapter. The service looks like this:\n\nclass CustomerService {\nconstructor(\nprivate readonly createCustomer: CreateDatabaseCustomerProtocol,\n) {}\n\npublic register(customer: CustomerInputEntity): SuccessfulEntityCreation {\nreturn this.createCustomer.createCustomerOnDatabase(customer);\n}\n}\nFor the service, it does not matter whether the database returns JSON, XML, or another format — the adapter translates to the contract the business rule expects.\n\n## Advantages\n\n- **Maintenance:** any plugin can be replaced without rewriting the service.\n\n- **Tests:** to test only the business rule, inject a mock adapter implementing the same protocol.\n\n- **Clean code:** separated responsibilities; the business rule does not carry driver details.\n\n## Next step\n\nTake a frontend with dozens of libraries and identify what you actually use. Pick one feature — converting BRL to USD, for example. Describe the contract (input and output) and implement an adapter on top of the library that does it today. Repeat where the dependency hurts.\n\n## Relation to other patterns",
      "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
      "keywords": [
        "adapter",
        "service",
        "business",
        "rule",
        "database",
        "that",
        "interface",
        "mysql",
        "does",
        "plugin"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/en/articles/design-patterns-adapter"
      }
    },
    {
      "id": "960361ba709111ca",
      "url": "https://imrafaeldev.site/en/articles/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 2)",
      "content": "I saw this very concretely in a commission-calculation process at a betting company. The application handled millions of bets per day, and part of the flow computed commission over several bet batches.\n\nAt first, the Node.js process worked. Sequentially it was correct, but slow. When I tried to parallelize with the usual logic of several tasks at once, the limit appeared: the bottleneck was CPU.\n\nIt was not just waiting on database, API, or external events. It was computation over a large bet buffer.\n\nThat is the kind of scenario where Promise.all can mislead. It gives a sense of parallelism, but it does not automatically turn heavy CPU work into real parallel execution. If the tasks are compute-intensive and run on the same main thread, the Event Loop stays busy.\n\nThe result can be worse than expected: loop blocking, higher latency, worse responsiveness, and more pressure on CPU and memory.\n\nThe problem was not Node.js being bad. The problem was using the default Node model for a load that demanded another kind of execution.\n\n## Where Go fit better\n\nThe solution was rewriting that process in Go with goroutines. The idea was to split the calculation into smaller chunks, process those pieces in parallel, and synchronize only at the end.\n\nThat design fit the problem better because the work was CPU bound and divisible. Instead of a centralized flow trying to coordinate several heavy operations, processing became distributed across smaller execution units.\n\nWith a worker pool, for example, you can control the goroutine count, limit fan-out, use available cores better, and avoid firing unbounded work.\n\nThe gain showed up. Total time dropped about 25%. The process that sat around 30 seconds started running near 22.5 seconds. CPU usage also improved.\n\nBut the important part of the story is not “Go fixed it”. The important part is that Go fixed one side of the problem and revealed another.\n\n## The bottleneck can move",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "loop",
        "event",
        "problem",
        "work",
        "goroutines",
        "same",
        "concurrency"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/en/articles/goroutines-vs-event-loop"
      }
    },
    {
      "id": "9628d830febdc9d2",
      "url": "https://imrafaeldev.site/en/resume",
      "title": "Resume (Part 1)",
      "content": "- [Home](/en/)\n- Resume\n\n## Engineering for systems that stopped being simple\n\nSenior Backend Engineer\n\nSenior Backend Engineer with more than seven years of experience in distributed systems, transactional platforms, and legacy modernization.\n\nI combine hands-on delivery with architecture, technical leadership, mentoring, and collaboration with product and stakeholders. My primary axis is Node.js, Go, and Java; React and Angular are complementary work across products that need continuity between backend and frontend.\n\n## Experience\n\n- Feb 2025 to May 2026 Remote Infosistemas Senior Software Engineer / Software Architect Expand experience Collapse experience\nLed NestJS and Go integrations and redesigned flows between microservices, reducing intermittent failures in critical flows by approximately 98%.\n\nContext\n\nWorked on management platforms for rental companies, fleets, and automakers, within the architecture team and alongside operations and data specialists.\n\nContribution\n\nLed NestJS and Go integrations and redesigned flows between microservices, making failure behavior more predictable in critical flows.\n\n[Read the full experience](/en/experience/infosistemas/)\n\n- Jul 2025 to Dec 2025 Remote EDS (Civil Police of Rio de Janeiro) Backend Engineer — Consulting Expand experience Collapse experience\nStructured a health management system with NestJS for a critical public operation and evolved backend routes with a focus on security, access, and traceability.\n\nContext\n\nThe consulting work covered sensitive public systems, including a high-volume health management system and an evolving legal ERP.\n\nContribution\n\nStructured the NestJS backend, refactored legacy routes, and strengthened security, access control, and traceability for sensitive flows.\n\n[Read the full experience](/en/experience/eds-policia-civil-rio/)",
      "description": "Rafael Pereira",
      "keywords": [
        "experience",
        "with",
        "nestjs",
        "full",
        "engineer",
        "node",
        "backend",
        "remote",
        "expand",
        "collapse"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/en/resume"
      }
    },
    {
      "id": "964e58bf5043ad51",
      "url": "https://imrafaeldev.site/artigos/intensivao-golang-avancado",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs\n\n## Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs\n\nO passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.\n\n22 de setembro de 2026\n\n- [Arquitetura](/artigos/?topic=arquitetura)\n- [Performance](/artigos/?topic=performance)\n- [Trade-offs](/artigos/?topic=trade-offs)\n\nEste guia é o aprofundamento do roteiro de 30 minutos de Go. Ele parte dos mesmos fundamentos — goroutines, channels, contexto, backpressure e idempotência — para discutir com mais calma quando cada decisão de desenho se aplica.\n\nSe você ainda não passou pela revisão rápida, comece pelo [roteiro de 30 minutos](/artigos/intensivao-go/) e volte aqui para aprofundar cada bloco.\n\n## Como usar este guia\n\nAvance na ordem proposta: cada seção isola uma decisão de desenho, descreve o mecanismo, a falha que ele trata, um exemplo autocontido e o critério para escolher outra abordagem. Todos os cenários abaixo são didáticos e hipotéticos — servem para treinar leitura de trade-offs, não descrevem sistemas reais. Leia com um editor aberto e adapte os snippets ao seu próprio exercício antes de levar qualquer padrão para um sistema real.\n\n## 1. Modelo de execução: goroutines, scheduler e GOMAXPROCS\n\n**Mecanismo.** Goroutines são unidades leves de execução multiplexadas pelo scheduler do runtime sobre threads do sistema operacional. GOMAXPROCS define quantas threads podem executar código Go simultaneamente; por padrão, acompanha o número de CPUs disponíveis. Desde o Go 1.14, as goroutines são assincronamente preemptíveis: o scheduler pode interrompê-las mesmo em loops apertados sem pontos explícitos de cooperação, distribuindo trabalho sem que cada tarefa exija uma thread dedicada.",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "goroutines",
        "sync",
        "func",
        "contexto",
        "quando",
        "done",
        "mutex",
        "limite",
        "context"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-golang-avancado"
      }
    },
    {
      "id": "96dbfecb0977b1b4",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-en",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 1)",
      "content": "The thesis is simple: Node.js with the Event Loop and Go with goroutines do not solve the same kind of problem the same way. The comparison goes bad when we treat both as direct competitors in every scenario. In practice, the most common mistake is confusing concurrency with parallelism.\n\nConcurrency is organizing several tasks that may be underway at the same time. Parallelism is actually executing work at the same time, using multiple CPU cores. That difference sounds academic until it shows up in production.\n\nThe Node.js Event Loop is very good when the bottleneck is waiting: external APIs, databases, WebSockets, user input, queues, and events. While one operation waits for a response, the loop keeps serving other tasks. It is the corner-store owner at the counter: he does not stop because he asked someone to fetch candy from the stockroom.\n\nGoroutines, on the other hand, start looking more interesting when the work is CPU bound, divisible, and can use multiple cores with explicit concurrency control. They are lightweight execution units managed by the Go runtime. With them, a task can be broken into smaller parts, distributed, and synchronized at the end.\n\nIt is more like a busy stall at the São João festival in Caruaru: one person grills corn, another stirs canjica, another slices bolo de rolo. Work advances at the same time, each person handling one part.\n\n## Where Node.js starts to suffer\n\nI saw this very concretely in a commission-calculation process at a betting company. The application handled millions of bets per day, and part of the flow computed commission over several bet batches.\n\nAt first, the Node.js process worked. Sequentially it was correct, but slow. When I tried to parallelize with the usual logic of several tasks at once, the limit appeared: the bottleneck was CPU.\n\nIt was not just waiting on database, API, or external events. It was computation over a large bet buffer.",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "work",
        "loop",
        "problem",
        "event",
        "time",
        "memory",
        "same"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models",
        "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
        "excerpt": "Node.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-en.md"
      }
    },
    {
      "id": "97033cac93871395",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 2)",
      "content": "Fue cuando abrimos el plan de ejecución. La pantalla devolvía pocos indicadores, pero la consulta atravesaba relaciones, formaba un volumen intermedio grande y gastaba CPU en agregaciones antes de llegar a ellos. La respuesta final era pequeña. El trabajo para producirla, enorme.\n\nDuplicar CPU y RAM había dado más aire a la base, pero la búsqueda seguía obligándola a hacer todo a la vez. El plan mostró que la investigación necesitaba entrar en el camino recorrido por la consulta.\n\n## Lo que necesito ver antes de tocar la base\n\nPara que la base se vuelva sospechosa de verdad, quiero el plan de ejecución y las métricas apuntando en esa dirección. Tiempo de la consulta, lecturas y cardinalidad suelen confirmar o eliminar una hipótesis en pocos minutos.\n\nSi esas medidas están saludables, saco la base del frente. Ya tomé API lenta con la consulta respondiendo dentro de lo esperado, mientras el retraso estaba en el procesamiento de la aplicación y en llamadas remotas hechas de forma secuencial. A partir de ahí, seguir buscando un defecto en la base sería solo insistir en la capa equivocada.\n\nAntes de profundizar, paso un radar corto por la consulta y por el código. Una query para cargar la lista seguida de otra para cada registro pide un conteo de consultas. Ese N+1 aparece con frecuencia cuando el ORM deja las relaciones para que el backend las busque una a una.\n\nUn JOIN que multiplica filas antes de la agregación pide la medición del volumen intermedio. Índice ausente pide plan. Un filtro que aplica una función sobre la columna indexada también.\n\nEn el código, busco llamadas remotas o consultas con `await` dentro de `for`. Cada espera puede parecer pequeña aisladamente y aun así dominar el tiempo total cuando todas ejecutan en fila.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "97f544fe9c55a482",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 2)",
      "content": "Foi quando abrimos o plano de execução. A tela devolvia poucos indicadores, mas a consulta atravessava relacionamentos, formava um volume intermediário grande e gastava CPU em agregações antes de chegar neles. A resposta final era pequena. O trabalho para produzi-la, enorme.\n\nDobrar CPU e RAM tinha dado mais fôlego ao banco, mas a busca continuava obrigando-o a fazer tudo de uma vez. O plano mostrou que a investigação precisava entrar no caminho percorrido pela consulta.\n\n## O que preciso ver antes de mexer no banco\n\nPara o banco virar suspeito de verdade, quero o plano de execução e as métricas apontando nessa direção. Tempo da consulta, leituras e cardinalidade geralmente confirmam ou eliminam uma hipótese em poucos minutos.\n\nSe essas medidas estiverem saudáveis, tiro o banco da frente. Já peguei API lenta com a consulta respondendo dentro do esperado, enquanto o atraso estava no processamento da aplicação e em chamadas remotas feitas de forma sequencial. A partir daí, continuar procurando um defeito no banco seria apenas insistir na camada errada.\n\nAntes de aprofundar, passo um radar curto pela consulta e pelo código. Uma query para carregar a lista seguida de outra para cada registro pede uma contagem de consultas. Esse N+1 aparece com frequência quando o ORM deixa as relações para o backend buscar uma a uma.\n\nUm JOIN que multiplica linhas antes da agregação pede a medição do volume intermediário. Índice ausente pede plano. Um filtro que aplica uma função sobre a coluna indexada também.\n\nNo código, procuro chamadas remotas ou consultas com `await` dentro de `for`. Cada espera pode parecer pequena isoladamente e ainda assim dominar o tempo total quando todas executam em fila.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "9848fd8d6b9a3c76",
      "url": "https://imrafaeldev.site/projetos",
      "title": "Projetos (Part 1)",
      "content": "- [Início](/)\n- Projetos\n\n## Projetos\n\nArtefatos com problema, restrições e estado atual do repositório.\n\nRepositório público\n\n## [SMS Manager](/projetos/sms-manager/)\n\nImportar CSV não pode travar a API Nest. A campanha persiste e publica na fila RabbitMQ; consumidores em TypeScript, Go ou Rust gravam o resultado no Mongo. A API não espera a operadora.\n\n[Abrir o projeto](/projetos/sms-manager/)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\nExperimento documentado\n\n## [goc_mcp](/projetos/goc-mcp/)\n\nMaestro (Codex/Cursor) delega via MCP; daemon FIFO e workers OpenCode executam com estado em SQLite. A orquestração funcionou, mas a medição não confirmou redução de custo ou tempo.\n\n[Abrir o projeto](/projetos/goc-mcp/)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\nProduto próprio, desktop\n\n## [md2cv](/projetos/md2cv/)\n\nPerfil e versões imutáveis ficam no SQLite da máquina. O agente supervisionado só propõe alterações sob schema; ATS e exportação em PDF/DOCX reutilizam o mesmo grafo, sem backend SaaS dono dos dados.\n\n[Abrir o projeto](/projetos/md2cv/)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\nCLI pública; revisão por IA ainda mock\n\n## [DiffVision](/projetos/diffvision/)\n\nO diff Git abre na UI local; comentários e exportação em Markdown ficam no repositório. A revisão visual por IA permanece mock.\n\n[Abrir o projeto](/projetos/diffvision/)\n\nRevisão local-first\nDiff no disco, UI local, comentários e export em `.diffvision/`. A plataforma remota fica de fora; a revisão por IA visual ainda é mock.\n\nEstação de trabalho editorial\n\n## [Post Engine](/projetos/post-engine/)",
      "description": "Artefatos com problema, restrições e estado atual do repositório.",
      "keywords": [
        "projetos",
        "não",
        "abrir",
        "projeto",
        "para",
        "sqlite",
        "local",
        "revisão",
        "diffvision",
        "estado"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/projetos"
      }
    },
    {
      "id": "99aab73861f35547",
      "url": "https://imrafaeldev.site/es/casos/mensajeria-rabbitmq",
      "title": "Infosistemas: contrato de fallo en la mensajería RabbitMQ (Part 1)",
      "content": "- [Inicio](/es/)\n- [Casos](/es/casos/)\n- Infosistemas: contrato de fallo en la mensajería RabbitMQ\n\nInfosistemas\n\n## Infosistemas: contrato de fallo en la mensajería RabbitMQ\n\nFallos intermitentes entre microservicios sin contrato para retry, DLQ o duplicidad. Más consumidores solo desplazaban la sobrecarga.\n\nIngeniero de Software Sénior / Arquitecto de Software feb/2025 – may/2026\n\n~98% reducción de fallos intermitentes en los flujos críticos\n\nContrato de falha na mensageria\nPublicação confirmada, consumo com prefetch controlado, retry com backoff e DLQ por fluxo. Escalar só consumidores fica de fora do desenho.\n\n- [01Contexto](#contexto)\n- [02Restricciones](#restricciones)\n- [03Problema](#problema)\n- [04Decisión](#decision)\n- [05Alternativa descartada](#alternativa-descartada)\n- [06Implementación](#implementacion)\n- [07Resultado](#resultado)\n- [08Limitaciones](#limitaciones)\n\n## Contexto\n\nInfosistemas opera plataformas de gestión para arrendadoras, flotas y automotrices. El trabajo ocurrió en el equipo de arquitectura, en colaboración con DevOps, SREs y DBAs, entre febrero de 2025 y mayo de 2026.\n\nEste caso cubre el frente de mensajería. Otros frentes de la misma experiencia (seguridad del ERP, jornadas de firma, integraciones fiscales) existen en las fuentes, pero no entran aquí como número o afirmación extra.\n\n## Restricciones\n\nLos flujos críticos cruzaban microservicios. El fallo era intermitente: la misma operación podía completarse en una ejecución y no completarse en la siguiente. Aumentar concurrencia o prefetch sin criterio transfería sobrecarga a consumidores, servicios o bases downstream.\n\n## Problema\n\nLos mensajes dejaban de completar el flujo esperado. Investigar un fallo parcial era difícil. No había contrato explícito para fallo temporal, fallo permanente, duplicidad o poison message.\n\n## Decisión\n\nLa mensajería fue rediseñada para volver predecible el comportamiento ante fallos:\n\n- colas durables;",
      "description": "Rediseño de la mensajería RabbitMQ en Infosistemas con colas durables, DLQ, retry, idempotencia y prefetch. El trabajo redujo en cerca del 98% los fallos intermitentes entre microservicios.",
      "keywords": [
        "fallo",
        "para",
        "contrato",
        "prefetch",
        "consumidores",
        "flujos",
        "imrafaeldev",
        "infosistemas",
        "mensajería",
        "fallos"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/es/casos/mensajeria-rabbitmq"
      }
    },
    {
      "id": "9a45cf5084983269",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 9)",
      "content": "// core/protocols/UpdateUserProtocol.ts\nexport abstract class UpdateUserProtocol {\n  abstract update(id: string, name: string): Promise<UserEntity>;\n}\n\n// core/protocols/DeleteUserProtocol.ts\nexport abstract class DeleteUserProtocol {\n  abstract delete(id: string): Promise<void>;\n}\n```\n\n## Conclusion\n\nSeparating Core, Adapters, and Infra leaves business rules testable without Nest, Prisma, or HTTP. Swapping databases or input channels becomes an adapter swap plus DI wiring — not a usecase rewrite. The cost is more files and boundary discipline; the gain shows when the system must change without dragging the domain along.\n\nOriginally published on [Medium](https://medium.com/@contato.dev.rafael.pereira/typescript-cleanarch-668935d677c2) (03/15/2023).",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 8,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "9a641ab8702e53a8",
      "url": "https://imrafaeldev.site/articles/desenoxidando-logica-container-with-most-water-es",
      "title": "Desenoxidando la lógica #02: Container With Most Water (Part 1)",
      "content": "Después de resolver el primer ejercicio de la serie, seguí en LeetCode para trabajar la lógica sin pedir una solución lista. El desafío 011 es [Container With Most Water](https://leetcode.com/problems/container-with-most-water/).\n\nRecibimos un array de alturas y necesitamos elegir dos líneas que formen el recipiente con el área mayor.\n\n## Cómo calcular el área\n\nSi elijo las posiciones `left` y `right`, el ancho es la distancia entre ellas. La altura del recipiente está limitada por la menor de las dos líneas.\n\n```text\nárea = min(altura izquierda, altura derecha) × distancia\n```\n\nPor ejemplo, con estas alturas:\n\n```text\n[1, 8, 6, 2, 5, 4, 8, 3, 7]\n```\n\nLas líneas en las posiciones `1` y `8` tienen alturas `8` y `7`. La menor altura es `7`, y la distancia entre ellas es `7`. Esa combinación produce un área de `49`.\n\n## Mi primer intento\n\nEmpecé con dos punteros, uno en cada punta del array. Después de calcular el área actual, simulaba dos posibilidades:\n\n- avanzar el puntero de la izquierda;\n- retroceder el puntero de la derecha.\n\nCalculaba el área de los dos próximos pares y elegía la mayor. El tramo principal era este:\n\n```javascript\nconst paddingLeftArea =\n  Math.min(heights[leftIndex + 1], heights[rigthIndex]) *\n  (rigthIndex - leftIndex + 1);\n\nconst paddingRightArea =\n  Math.min(heights[leftIndex], heights[rigthIndex - 1]) *\n  (rigthIndex - 1 - leftIndex);\n\nif (paddingLeftArea > paddingRightArea && paddingLeftArea > maxArea) {\n  leftIndex += 1;\n} else {\n  rigthIndex -= 1;\n}\n```\n\nEl problema estaba en la hipótesis. La mejor decisión local no garantiza la mejor área en el resto del array. Intentaba adivinar el camino mirando solo los dos próximos movimientos. También había un error en la fórmula de la distancia de ese borrador: para un par de posiciones, el ancho es `right - left`.\n\n## La observación que destraba el problema\n\nEl área depende de dos cosas: ancho y menor altura.",
      "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
      "keywords": [
        "área",
        "heights",
        "leftindex",
        "rigthindex",
        "altura",
        "punteros",
        "maxarea",
        "javascript",
        "leetcode",
        "mayor"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "desenoxidando-logica-container-with-most-water",
        "title": "Desenoxidando la lógica #02: Container With Most Water",
        "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
        "excerpt": "Intenté elegir el próximo paso mirando solo a los vecinos. Funcionó en algunos casos, pero el problema pedía una visión más amplia.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/desenoxidando-logica-container-with-most-water-es.md"
      }
    },
    {
      "id": "9a9e7d7e00edffff",
      "url": "https://imrafaeldev.site/es/articulos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 3)",
      "content": "Pero la parte importante de la historia no es “Go lo resolvió”. La parte importante es que Go resolvió un lado del problema y reveló otro.\n\n## El cuello de botella puede cambiar de lugar\n\nLa primera dificultad fue garantizar que el resultado en Go fuera igual al resultado en Node.js. Esto es menos glamoroso que hablar de concurrencia, pero es lo que separa la optimización real de la regresión enmascarada.\n\nSi el cálculo se vuelve más rápido y cambia el resultado financiero, la mejora no vale nada.\n\nDespués de eso, el problema principal se volvió la división de los chunks. La estrategia inicial consumía demasiada memoria. En pruebas locales, con datasets menores, el crecimiento proporcional llegó cerca del 15% en algunos momentos.\n\nLa incomodidad venía de una expectativa equivocada: pensé que cambiar a Go resolvería automáticamente el problema. En la práctica, solo había movido el cuello de botella. Antes el límite estaba más claro en CPU. Después, la estrategia de particionamiento empezó a presionar la memoria.\n\nEsto puede pasar por varios motivos: copias innecesarias, buffers grandes, slices manteniendo referencia a arrays mayores, colas internas demasiado grandes o exceso de trabajo preparado antes de procesarse.\n\nEn el resultado final, la memoria aún creció cerca del 5%. En ese caso, la ganancia de tiempo y CPU compensó la pérdida. Pero esto no es una regla universal. Si la carga de producción fuera mucho mayor, o si el servicio corriera con poco margen de memoria, ese intercambio podría dejar de ser aceptable.\n\nEl paralelismo cuesta coordinación, asignación, sincronización y observabilidad. No existe ejecución paralela gratis.\n\n## Cuándo mantendría Node.js\n\nMantendría Node.js sin incomodidad para orquestación de I/O: comunicación por WebSocket, llamadas a varias APIs, consultas en base, disparo de eventos, integración entre servicios y flujos donde el tiempo muerto está en la espera.",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "loop",
        "mismo",
        "trabajo",
        "más",
        "goroutines",
        "event",
        "concurrencia",
        "problema"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "9ace43f7d730ba71",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 7)",
      "content": "Cache, fila, índice e máquina maior continuam disponíveis. Não são decisões proibidas. Só não deveriam entrar antes de sabermos qual trabalho está segurando a resposta.\n\nAntes de mudar, defino a medida que precisa melhorar e a que não pode piorar. Depois do deploy, volto ao plano, às métricas e ao profiling. Se a métrica-alvo não melhora, a hipótese não ficou de pé. Se a medida de proteção piora, a solução criou outro problema.\n\nDescarto a hipótese e retorno ao ponto do fluxo em que o tempo ainda cresce.\n\n---\n\n*Publicado originalmente no [LinkedIn](https://www.linkedin.com/pulse/maioria-dos-problemas-de-performance-backend-come%C3%A7a-perto-s-pereira-zqlae/) em 16 de julho de 2026.*",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 6,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "9c3e2a304d04f485",
      "url": "https://imrafaeldev.site/en/projects/goc-mcp",
      "title": "goc_mcp",
      "content": "- [Home](/en/)\n- [Projects](/en/projects/)\n- goc_mcp\n\nDocumented experiment\n\n## goc_mcp\n\nMaestro (Codex/Cursor) delegates over MCP; FIFO daemon and OpenCode workers execute with state in SQLite. Orchestration worked, but measurement did not confirm cost or time reduction.\n\n[Repository](https://github.com/imrafaeldev/goc_mcp)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\n- [01Problem](#problem)\n- [02Constraints](#constraints)\n- [03Decision](#decision)\n- [04Current state](#current-state)\n- [05Limitations](#limitations)\n\n## Problem\n\nA maestro (Codex or Cursor) needs to delegate work to OpenCode workers with an explicit lifecycle: plan, start, follow, respond, cancel, and recover, without infinite agent recursion.\n\n## Constraints\n\nEverything is local. The daemon authenticates on loopback. There are global and per-workspace limits. Interrupted tasks must be reconciled. State corruption must not become blind writes. On the happy path: one OpenCode worker at a time, without automatic decomposition that leaves the maestro without control.\n\n## Decision\n\nGo implementation: MCP gateways over stdio, single daemon, state machine, and FIFO executor. SQLite persistence with WAL; JSONL artifacts. OpenCode adapter with server as the primary path and CLI as fallback. Isolation against recursive delegation and read-only diagnostics if state corrupts.\n\n## Current state\n\nRepository with tests, ADRs, and benchmarks. Orchestration worked. The measurements published in the project itself did not confirm the hypothesis of reducing time and cost.\n\n## Limitations\n\nIt is an experiment. It does not claim agent-engineering productivity gains in production. The negative result of the hypothesis is part of the artifact.",
      "description": "Local agent orchestration over MCP in Go: maestro, FIFO daemon, OpenCode workers, and SQLite. Delivery worked, but the cost and time hypothesis was not confirmed in this measurement.",
      "keywords": [
        "state",
        "opencode",
        "with",
        "maestro",
        "daemon",
        "fifo",
        "sqlite",
        "time",
        "without",
        "projects"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/projects/goc-mcp"
      }
    },
    {
      "id": "9c88cc66662cccde",
      "url": "https://imrafaeldev.site/artigos/intensivao-go",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos\n\n## Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos\n\nUm roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.\n\n22 de setembro de 2026\n\n- [Arquitetura](/artigos/?topic=arquitetura)\n- [Performance](/artigos/?topic=performance)\n- [Mensageria](/artigos/?topic=mensageria)\n\nEste roteiro serve para quem já trabalha com backend e quer reativar Go para uma conversa técnica ou um serviço de produção. O foco está nas decisões que mantêm um sistema previsível sob carga: concorrência limitada, cancelamento, filas finitas, idempotência e observabilidade.\n\nO recorte usa Go 1.26. Não é uma introdução à linguagem. Passe rápido pelos fundamentos e retenha os pontos que mudam o desenho de um consumer, uma API ou um pipeline de telemetria.\n\n## Roteiro de 30 minutos\n\nTempo\n\nBloco\n\nPrioridade\n\n0-4 min\n\nTipos, structs, interfaces e erros\n\nRevisão rápida\n\n4-10 min\n\nGoroutines, channels, select e contexto\n\nAlta\n\n10-17 min\n\nLimites de concorrência e backpressure\n\nMáxima\n\n17-23 min\n\nPipeline IoT resiliente\n\nMáxima\n\n23-26 min\n\nRuntime, memória e profiling\n\nAlta\n\n26-30 min\n\nArquitetura e perguntas de entrevista\n\nMáxima\n\n## Fundamentos que aparecem em produção\n\nGo favorece composição, contratos pequenos e fluxo explícito. Não há herança de classes nem exceções como mecanismo normal de controle. Um tipo simples pode carregar sua própria validação:\n\npackage telemetry\n\nimport (\n\"errors\"\n\"fmt\"\n\"time\"\n)\n\nvar ErrOutOfRange = errors.New(\"reading out of range\")\n\ntype Reading struct {\nDeviceID string `json:\"device_id\"`\nSequence uint64 `json:\"sequence\"`\nObservedAt time.Time `json:\"observed_at\"`\nValue float64 `json:\"value\"`\n}",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "reading",
        "não",
        "para",
        "return",
        "cancelamento",
        "value",
        "quando",
        "context",
        "concorrência",
        "errors"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-go"
      }
    },
    {
      "id": "9ccc2a3c47d7cfa4",
      "url": "https://imrafaeldev.site/experiences/sustentec-es",
      "title": "Sustentec | Rafael Pereira",
      "content": "## Contexto\n\nEn Sustentec, trabajé en sistemas relacionados con laboratorios, investigación y desarrollo. La experiencia combinó el mantenimiento de un producto existente con la evolución de funcionalidades e integraciones.\n\n## Cómo trabajé\n\nDesarrollé una API REST en Dart con Shelf para integrar bases de instituciones de investigación. También mantuve y evolucioné un sistema de gestión de laboratorios con Java, Spring Boot, JPA, Hibernate, PostgreSQL y Angular.\n\nImplementé pruebas de integración donde antes no existía esa cobertura, desarrollé informes y funcionalidades de extremo a extremo y participé en la recopilación de requisitos con clientes y el refinamiento de sprints con el Product Owner.\n\n## Lo que me llevé\n\nEsta experiencia reforzó que la calidad no se limita a pruebas unitarias. En un sistema con varias capas, necesitaba validar el comportamiento real entre API, persistencia e interfaz.",
      "description": "Mi experiencia en Sustentec con sistemas de investigación, APIs y calidad.",
      "keywords": [
        "trabajé",
        "laboratorios",
        "investigación",
        "experiencia",
        "funcionalidades",
        "desarrollé",
        "sistema",
        "pruebas",
        "extremo",
        "contexto"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-sustentec",
        "slug": "sustentec",
        "title": "Sustentec | Rafael Pereira",
        "description": "Mi experiencia en Sustentec con sistemas de investigación, APIs y calidad.",
        "company": "Sustentec",
        "role": "Ingeniero de Software Full Stack Pleno",
        "period": "ene/2021 a ago/2021",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/sustentec-es.md"
      }
    },
    {
      "id": "9d8d196e4447900a",
      "url": "https://imrafaeldev.site/en/articles/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 2)",
      "content": "## Layer guide\n\nExample: user CRUD over REST with NestJS. Installation details are out of scope. **Core-to-infra** writing (inside out).\n\n## Core\n\nIn the classic design, *domain* and *entities* sit very close. Here they form the **Core**: everything the business rule *is* — features and domain representations.\n\nIn the example, the main entity is User (id, name), in core/entities.\n\n### Entities\n\n// core/entities/UserEntity.ts\nexport interface UserEntityProps {\nid?: string;\nname: string;\n}\n\nexport class UserEntity {\nconstructor(private readonly props: UserEntityProps) {}\n\nget id(): string {\nreturn this.props.id ?? \"\";\n}\n\nget name(): string {\nreturn this.props.name;\n}\n}\nThe entity receives typed props and exposes getters. It depends on an interface any transfer DTO can satisfy later.\n\n### Features and usecases\n\nCRUD needs create, fetch, update, and remove. In Core, each usecase implements a contract (feature) with a single public method — aligned with Liskov, open/closed, interface segregation, and single responsibility. The usecase does **not** access the database: it knows **protocols** describing the external action (dependency inversion).\n\nRegistration: name is required; if it already exists, error; otherwise return UserEntity.\n\n- contract CreateUser\n\n- implementation CreateUserUsecase\n\nIn TypeScript, an abstract class with abstract methods works as both contract *and* value — useful for DI (const createUserSymbol = CreateUser):\n\n// core/features/CreateUser.ts\nexport abstract class CreateUser {\nabstract execute(name: string): Promise&#x3C;UserEntity>;\n}\n// core/usecases/CreateUserUsecase.ts\nexport class CreateUserUsecase implements CreateUser {\nconstructor(\nprivate readonly createUserProtocol: CreateUserProtocol,\nprivate readonly getByNameProtocol: GetUserByNameProtocol,\n) {}\n\nasync execute(name: string): Promise&#x3C;UserEntity> {\nconst existsName = await this.getByNameProtocol.getByName(name);",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "architecture",
        "return",
        "export",
        "class",
        "adapters"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/en/articles/typescript-clean-architecture"
      }
    },
    {
      "id": "9e6b803e47ec04cc",
      "url": "https://imrafaeldev.site/es/articulos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 2)",
      "content": "## El punto donde Node.js empieza a sufrir\n\nLo vi de forma muy concreta en un proceso de cálculo de comisión en una casa de apuestas. La aplicación lidiaba con millones de apuestas por día, y parte del flujo implicaba calcular comisión sobre varios lotes de apuestas.\n\nAl principio, el proceso en Node.js funcionaba. Secuencialmente era correcto, pero lento. Cuando intenté paralelizar con la lógica común de varias tareas al mismo tiempo, apareció el límite: el cuello de botella era CPU.\n\nNo era solo esperar base, API o evento externo. Era cálculo sobre un buffer grande de apuestas.\n\nEse es el tipo de escenario en que Promise.all puede engañar. Da sensación de paralelismo, pero no transforma automáticamente trabajo pesado de CPU en ejecución paralela real. Si las tareas son cómputo intenso y corren en el mismo hilo principal, el Event Loop queda ocupado.\n\nEl resultado puede ser peor de lo esperado: bloqueo del loop, aumento de latencia, peor responsividad y mayor presión sobre CPU y memoria.\n\nEl problema no era que Node.js fuera malo. El problema era usar el modelo estándar de Node para una carga que exigía otro tipo de ejecución.\n\n## Donde Go encajó mejor\n\nLa solución fue reescribir ese proceso en Go usando goroutines. La idea era dividir el cálculo en chunks menores, procesar esos pedazos en paralelo y sincronizar solo al final.\n\nEse diseño encajaba mejor en el problema porque el trabajo era CPU bound y podía dividirse. En lugar de un flujo centralizado intentando coordinar varias operaciones pesadas, el procesamiento pasó a distribuirse en unidades menores de ejecución.\n\nCon un worker pool, por ejemplo, se puede controlar el número de goroutines, limitar el fan-out, usar mejor los cores disponibles y evitar que el sistema dispare trabajo sin límite.\n\nLa ganancia apareció. El tiempo total cayó cerca del 25%. El proceso que quedaba en torno a los 30 segundos pasó a correr en algo cercano a 22,5 segundos. También hubo mejor uso de CPU.",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "loop",
        "mismo",
        "trabajo",
        "más",
        "goroutines",
        "event",
        "concurrencia",
        "problema"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "9e93e41d542bb9de",
      "url": "https://imrafaeldev.site/projects/md2cv-en",
      "title": "md2cv (Part 2)",
      "content": "Public repository under MIT, documentation portal, and x64 releases for Linux (AppImage, `.deb`, `.rpm`) and Windows (NSIS and portable), with CI and Vitest and Playwright tests on persistence, agents, ATS, and Electron flows.\n\nThe professional export used on this site comes from this product and is not read by the site at runtime.\n\n## Limitations\n\nIt does not replace human review nor promise hiring or automatic approval by recruiting platforms. Job matching depends on confirmed answers; the system refuses to fabricate experience.\n\nWindows binaries in this phase are not code-signed. The project is author-owned with no commercial purpose from the maintainer; the MIT license allows reuse under its terms.",
      "description": "Local-first desktop studio for professional profiles, Markdown resumes, immutable versions, ATS, and job matching with supervised agents. Data stays in the machine's SQLite.",
      "keywords": [
        "resume",
        "with",
        "version",
        "graph",
        "agents",
        "profile",
        "product",
        "from",
        "state",
        "under"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "projeto-md2cv",
        "slug": "md2cv",
        "title": "md2cv",
        "description": "Local-first desktop studio for professional profiles, Markdown resumes, immutable versions, ATS, and job matching with supervised agents. Data stays in the machine's SQLite.",
        "excerpt": "Profile and immutable versions stay in the machine's SQLite. The supervised agent only proposes changes under schema; ATS and PDF/DOCX export reuse the same graph, with no SaaS backend owning the data.",
        "repoUrl": "https://github.com/imrafaeldev/md2cv",
        "status": "Own product, desktop",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "projects/md2cv-en.md"
      }
    },
    {
      "id": "9fb4d751fbe85eea",
      "url": "https://imrafaeldev.site/es/articulos",
      "title": "Artículos (Part 2)",
      "content": "[Trade-offs](/es/articulos/?topic=trade-offs)\n- [Rendimiento](/es/articulos/?topic=performance)\nNode.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.\n\n-\n\n## [TypeScript Clean Architecture: Core, Adapters e Infra](/es/articulos/typescript-clean-architecture/)\n\n15 de marzo de 2023\n\n[Arquitectura](/es/articulos/?topic=arquitetura)\nArquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.\n\n-\n\n## [Design Patterns: Strategy](/es/articulos/design-patterns-strategy/)\n\n5 de mayo de 2022\n\n[Arquitectura](/es/articulos/?topic=arquitetura)\nLas cadenas de if/else crecen y se vuelven frágiles. Strategy aísla cada algoritmo tras un contrato.\n\n-\n\n## [Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter](/es/articulos/design-patterns-adapter/)\n\n26 de abril de 2022\n\n[Arquitectura](/es/articulos/?topic=arquitetura)\nMigrar de MySQL a PostgreSQL no exige reescribir el servicio. El Adapter aísla el plugin tras un contrato que la regla de negocio entiende.",
      "description": "Patrones y decisiones de ingeniería en prosa técnica, con código.",
      "keywords": [
        "articulos",
        "topic",
        "arquitectura",
        "performance",
        "trade-offs",
        "2026",
        "arquitetura",
        "rendimiento",
        "concurrencia",
        "para"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/es/articulos"
      }
    },
    {
      "id": "a04d09f4c1e1f5cd",
      "url": "https://imrafaeldev.site/visual-projects/gabriel-nutricao-en",
      "title": "Gabriel | Sports Nutrition",
      "content": "Institutional site for Gabriel Pereira around sports nutrition, with real content and strategy for a training routine.",
      "description": "Institutional site for Gabriel, focused on sports nutrition for people who train.",
      "keywords": [
        "institutional",
        "site",
        "gabriel",
        "pereira",
        "around",
        "sports",
        "nutrition",
        "with",
        "real",
        "content"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "visual-gabriel-nutricao",
        "slug": "gabriel-nutrition",
        "title": "Gabriel | Sports Nutrition",
        "description": "Institutional site for Gabriel, focused on sports nutrition for people who train.",
        "excerpt": "Gabriel Pereira's institutional site for sports nutrition, with real content and strategy for a training routine.",
        "siteUrl": "https://gabriel-pereira-nutri.vercel.app/",
        "imageUrl": "https://gabriel-pereira-nutri.vercel.app/hero2-desktop.webp",
        "imageAlt": "Gabriel recording content in his home studio, with microphone, monitor, and guitar in the background.",
        "imageWidth": "590",
        "imageHeight": "738",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "visual-projects/gabriel-nutricao-en.md"
      }
    },
    {
      "id": "a08498ca0ad5dfce",
      "url": "https://imrafaeldev.site/cases/infosistemas-mensageria-pt-br",
      "title": "Infosistemas: contrato de falha na mensageria RabbitMQ (Part 1)",
      "content": "## Contexto\n\nA Infosistemas opera plataformas de gestão para locadoras, frotas e montadoras. O trabalho ocorreu no time de arquitetura, em colaboração com DevOps, SREs e DBAs, entre fevereiro de 2025 e maio de 2026.\n\nEste caso cobre a frente de mensageria. Outras frentes da mesma experiência (segurança de ERP, jornadas de assinatura, integrações fiscais) existem nas fontes, mas não entram aqui como número ou afirmação extra.\n\n## Restrições\n\nOs fluxos críticos cruzavam microsserviços. A falha era intermitente: a mesma operação podia completar numa execução e não completar na seguinte. Aumentar concorrência ou prefetch sem critério transferia sobrecarga para consumidores, serviços ou bancos downstream.\n\n## Problema\n\nMensagens deixavam de completar o fluxo esperado. Investigar falha parcial era difícil. Não havia contrato explícito para falha temporária, falha permanente, duplicidade ou poison message.\n\n## Decisão\n\nA mensageria foi redesenhada para tornar o comportamento em falha previsível:\n\n- filas duráveis;\n- DLQ por fluxo, para mensagem que não deve desaparecer nem repetir sem controle;\n- retry com backoff para indisponibilidade temporária;\n- idempotência e deduplicação no consumidor, porque entrega duplicada não pode repetir efeito de negócio;\n- publisher confirms, para reduzir incerteza na publicação;\n- ajuste de prefetch, em vez de abrir concorrência indiscriminada.\n\n## Alternativa descartada\n\nTratar o problema como falta de capacidade (mais consumidores, mais prefetch) sem mudar o contrato de falha. Isso moveria o gargalo e manteria perda ou duplicidade silenciosa.\n\n## Implementação\n\nO redesenho colocou esses mecanismos nos fluxos críticos: publicação confirmada, fila durável com prefetch controlado, consumidor idempotente, retry com backoff e DLQ por fluxo. A operação passou a ter um caminho previsível para falha temporária e para falha permanente, em vez de depender de reprocessamento ad hoc.\n\n## Resultado",
      "description": "Redesenho da mensageria RabbitMQ na Infosistemas com filas duráveis, DLQ, retry, idempotência e prefetch. O trabalho reduziu em cerca de 98% as falhas intermitentes entre microsserviços.",
      "keywords": [
        "para",
        "falha",
        "não",
        "prefetch",
        "fluxos",
        "outras",
        "frentes",
        "críticos",
        "microsserviços",
        "operação"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "infosistemas-mensageria",
        "slug": "mensageria-rabbitmq",
        "title": "Infosistemas: contrato de falha na mensageria RabbitMQ",
        "description": "Redesenho da mensageria RabbitMQ na Infosistemas com filas duráveis, DLQ, retry, idempotência e prefetch. O trabalho reduziu em cerca de 98% as falhas intermitentes entre microsserviços.",
        "company": "Infosistemas",
        "role": "Engenheiro de Software Sênior / Arquiteto de Software",
        "period": "fev/2025 a mai/2026",
        "excerpt": "Falhas intermitentes entre microsserviços sem contrato para retry, DLQ ou duplicidade. Mais consumidores só empurravam a sobrecarga.",
        "proofValue": "~98%",
        "proofLabel": "redução de falhas intermitentes nos fluxos críticos",
        "featuredClaimId": "infosistemas-rabbitmq-reliability",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/infosistemas-mensageria-pt-br.md"
      }
    },
    {
      "id": "a09d02bf91486ebf",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-en",
      "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern (Part 3)",
      "content": "*Originally published on [LinkedIn](https://www.linkedin.com/pulse/pare-de-ser-ref%C3%A9m-das-depend%C3%AAncias-diga-bem-vindo-ao-design-rafael/) on April 26, 2022.*",
      "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
      "keywords": [
        "adapter",
        "service",
        "database",
        "business",
        "rule",
        "that",
        "interface",
        "with",
        "readonly",
        "dependency"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern",
        "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
        "excerpt": "Migrating from MySQL to PostgreSQL does not require rewriting the service. The Adapter isolates the plugin behind a contract the business rule understands.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-en.md"
      }
    },
    {
      "id": "a0f109debf46322c",
      "url": "https://imrafaeldev.site/artigos/intensivao-go",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 3)",
      "content": "for reading := range jobs {\nif err := process(reading); err != nil {\n// tratar ou registrar o erro da mensagem\n}\n}\nO produtor fecha o channel quando não haverá novo envio. Enviar em channel fechado ou fechá-lo duas vezes causa panic. Receber de um channel fechado retorna o zero value e ok == false. Um channel nil bloqueia para sempre; dentro de select, ele desabilita o caso.\n\nselect combina envio, cancelamento e política de saturação. Sem default, a operação espera espaço ou cancelamento. Com default, ela rejeita imediatamente quando a fila está cheia:\n\nfunc enqueue(ctx context.Context, jobs chan&#x3C;- Reading, reading Reading) error {\nselect {\ncase jobs &#x3C;- reading:\nreturn nil\ncase &#x3C;-ctx.Done():\nreturn context.Cause(ctx)\n}\n}\ncontext.Context carrega cancelamento, deadline e metadados estritamente ligados à requisição. Receba-o como primeiro argumento, propague-o, chame todo cancel retornado e não guarde contexto em struct. Cancelar não encerra uma goroutine à força: os loops e operações bloqueantes precisam observar ctx.Done().\n\n## Concorrência limitada antes da pressão de memória\n\nUma goroutine por mensagem parece barata até um downstream ficar lento. A fila cresce, o heap cresce, o GC trabalha mais e o processo pode cair antes de a CPU parecer saturada.\n\nPara tarefas independentes que falham juntas, errgroup oferece espera, propagação do primeiro erro e cancelamento compartilhado. SetLimit estabelece o teto de concorrência.\n\nfunc ProcessBatch(ctx context.Context, batch []Reading) error {\ng, ctx := errgroup.WithContext(ctx)\ng.SetLimit(16)\n\nfor _, reading := range batch {\nreading := reading\ng.Go(func() error {\nif err := processOne(ctx, reading); err != nil {\nreturn fmt.Errorf(\"device %s: %w\", reading.DeviceID, err)\n}\nreturn nil\n})\n}",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "reading",
        "não",
        "para",
        "return",
        "cancelamento",
        "value",
        "quando",
        "context",
        "concorrência",
        "errors"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-go"
      }
    },
    {
      "id": "a2c6cfac0543b186",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 10)",
      "content": "**Falha ou limite que ele trata.** Sem instrumentação nas fronteiras, incidentes de concorrência são invisíveis: fila crescendo, workers saturados, retries multiplicando carga e timeouts encadeados aparecem apenas como \"lentidão\". O limite: instrumentação excessiva (logar cada iteração, cardinalidade alta em rótulos como IDs únicos) custa CPU, memória e armazenamento — e pode derrubar o próprio sistema observado.\n\n**Exemplo de aplicação.** Cenário didático: worker que registra correlação, latência e profundidade da fila sem dependências externas.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"time\"\n)\n\ntype ctxKey string\n\nconst requestIDKey ctxKey = \"request_id\"\n\nfunc processOrder(ctx context.Context, orderID string) {\n\tstart := time.Now()\n\tlogger := slog.With(\"request_id\", ctx.Value(requestIDKey), \"order\", orderID)\n\tlogger.InfoContext(ctx, \"inicio\")\n\tdefer func() {\n\t\t// Em um sistema real, observe este valor em um histograma.\n\t\tlogger.InfoContext(ctx, \"fim\", \"duracao_ms\", time.Since(start).Milliseconds())\n\t}()\n\ttime.Sleep(50 * time.Millisecond)\n}\n\nfunc main() {\n\tctx := context.WithValue(context.Background(), requestIDKey, \"req-123\")\n\tqueueDepth := 7 // em um sistema real, exponha como gauge\n\tslog.InfoContext(ctx, \"worker\", \"fila\", queueDepth)\n\tprocessOrder(ctx, \"pedido-7\")\n}\n```\n\n**Quando escolher outra abordagem.** Para depuração local e exercícios, logs estruturados bastam; adicione métricas quando precisar de alertas e comparação entre deploys, e traces quando o caminho atravessar múltiplos serviços ou filas. Se a cardinalidade explodir, agregue por rota padrão, código de status e nome de operação — nunca por ID individual. Se o custo de coleta ficar alto, amostre traces e mantenha logs de erro completos, não o inverso.\n\n## 8. Profiling: CPU, memória e goroutines bloqueadas",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 9,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "a54ad9fd42fe6b0e",
      "url": "https://imrafaeldev.site/es/articulos/backend-performance-cerca-de-los-datos",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 3)",
      "content": "Un JOIN que multiplica filas antes de la agregación pide la medición del volumen intermedio. Índice ausente pide plan. Un filtro que aplica una función sobre la columna indexada también.\n\nEn el código, busco llamadas remotas o consultas con await dentro de for. Cada espera puede parecer pequeña aisladamente y aun así dominar el tiempo total cuando todas ejecutan en fila.\n\nNinguna de estas señales cierra el diagnóstico. Un N+1 en una ruta con dos ítems puede tener impacto irrelevante, un índice nuevo puede ayudar poco en una columna con baja selectividad y un join voluminoso quizás sea necesario para producir el resultado. Por eso, cuento las consultas, cronometro el loop entero y reviso en el plan cuántas filas y lecturas se produjeron.\n\nEse radar solo elige la primera medición. La próxima capa de la investigación viene de la cuenta.\n\n## Una línea correcta puede desperdiciar el índice\n\nUna de esas líneas suele pasar desapercibida en la revisión. Devuelve los registros de un día entero.\n\nWHERE CAST(campo_data_hora AS date) = @data\nEl resultado de la pantalla parece correcto. En el plan, la historia puede ser otra. Aplicar CAST a la columna obliga a la consulta a transformar los valores antes de la comparación. Con un índice en campo_data_hora, esto puede impedir una búsqueda directa por el intervalo, aumentar bastante las lecturas e incluso llevar a un scan.\n\nCuando el pedido es por los registros de un día entero, calculo los bordes fuera de la columna.\n\nWHERE campo_data_hora >= @inicio_do_dia\nAND campo_data_hora &#x3C; @inicio_do_proximo_dia\nSi el inicio es el 16 de julio a la medianoche, el límite siguiente es el 17 de julio a la medianoche. Así entran todos los valores del día 16, incluso aquellos con fracciones de segundo al final, sin depender de 23:59:59.999.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "base",
        "consulta",
        "para",
        "plan",
        "puede",
        "antes",
        "ejecución",
        "datos",
        "trabajo",
        "cuando"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/backend-performance-cerca-de-los-datos"
      }
    },
    {
      "id": "a675275e05cb454f",
      "url": "https://imrafaeldev.site/projects/diffvision-en",
      "title": "DiffVision",
      "content": "## Problem\n\nReviewing a Git diff in a SaaS tool sends code away and mixes remote UI with local history. Review needs to work offline, with hunks, filters, bookmarks, and line-anchored comments.\n\n## Constraints\n\nPreferences and reports must live in the repository itself (`.diffvision/`). The npm CLI starts local backend and UI. Assistant integration cannot be sold as ready while it is still a prototype.\n\n## Decision\n\nCLI inspects Git, parses unified diff, and serves a web interface. Fastify backend with snapshot and WebSocket; React/Vite UI. Markdown/JSON export in the repository. `diffvision-mcp` package over stdio to summarize the repository, read patches, and record comments. The visual AI review assistant is declared mock/prototype; comment writing over MCP works.\n\n## Current state\n\nDistributed as an npm CLI, running local-first.\n\n## Limitations\n\nIt does not replace the GitHub review flow. The visual AI flow must not be read as a finished product.",
      "description": "Local-first npm CLI for reviewing Git diffs, with local UI, in-repo comments, Markdown/JSON export, and MCP server. Visual AI review remains a mock.",
      "keywords": [
        "with",
        "review",
        "repository",
        "diff",
        "local",
        "comments",
        "must",
        "backend",
        "assistant",
        "prototype"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "projeto-diffvision",
        "slug": "diffvision",
        "title": "DiffVision",
        "description": "Local-first npm CLI for reviewing Git diffs, with local UI, in-repo comments, Markdown/JSON export, and MCP server. Visual AI review remains a mock.",
        "excerpt": "The Git diff opens in the local UI; comments and Markdown export stay in the repository. Visual AI review remains a mock.",
        "repoUrl": "https://github.com/imrafaeldev/diffvision-app",
        "status": "Public CLI; AI review still mock",
        "featured": "false",
        "order": "4",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/diffvision-en.md"
      }
    },
    {
      "id": "a6b0850c1dd23bf1",
      "url": "https://imrafaeldev.site/experiences/azify-pt-br",
      "title": "Azify | Rafael Pereira",
      "content": "## O contexto\n\nNa Azify, atuei como consultor em infraestrutura financeira para fintechs e pequenos bancos. O produto reunia capacidades como Pix, transferências, cartões, carteira digital e outros serviços bancários.\n\n## Como atuei\n\nParticipei de decisões arquiteturais e ajudei a estruturar práticas de desenvolvimento para um ambiente em que consistência, segurança e estabilidade tinham impacto financeiro direto. Desenvolvi um motor de liquidação em NestJS com integrações a múltiplas exchanges, monitoramento de risco e controles de compliance.\n\nTambém trabalhei na integração de exchanges e blockchains aos fluxos transacionais e na evolução de uma plataforma BaaS multi-tenant com OAuth 2.0, JWT e criptografia. Com profiling de consultas, revisão de índices e ajuste do uso de Redis, reduzi em 30% a latência de APIs financeiras críticas.\n\n## O que levo\n\nEssa consultoria retomou temas recorrentes da minha trajetória, como pagamentos, multi-tenancy e sistemas transacionais, com um grau de responsabilidade ainda maior sobre autorização e consistência.",
      "description": "Minha consultoria na Azify com liquidação, BaaS e serviços financeiros.",
      "keywords": [
        "como",
        "atuei",
        "para",
        "consistência",
        "exchanges",
        "transacionais",
        "contexto",
        "azify",
        "consultor",
        "infraestrutura"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-azify",
        "slug": "azify",
        "title": "Azify | Rafael Pereira",
        "description": "Minha consultoria na Azify com liquidação, BaaS e serviços financeiros.",
        "company": "Azify",
        "role": "Engenheiro Backend Sênior — Consultoria",
        "period": "mar/2025 a jun/2025",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/azify-pt-br.md"
      }
    },
    {
      "id": "a7411756dae59bf3",
      "url": "https://imrafaeldev.site/es/experiencias/infosistemas",
      "title": "Infosistemas",
      "content": "- [Inicio](/es/)\n- Infosistemas\n\n## Infosistemas\n\nMi experiencia en Infosistemas con mensajería, integraciones y plataformas de movilidad.\n\nCargo\n\nIngeniero de Software Senior / Arquitecto de Software\n\nPeríodo\n\nfeb/2025 a may/2026\n\n## Contexto\n\nEn Infosistemas, trabajé en plataformas para arrendadoras, flotas, automotrices y operaciones de movilidad. Era un entorno enterprise de integraciones, flujos fiscales y servicios de alto volumen, donde trabajé junto a DevOps, SREs y DBAs.\n\n## Cómo trabajé\n\nMi trabajo combinó arquitectura y ejecución práctica. Rediseñé flujos entre microservicios, lideré integraciones en NestJS y Go e implementé trazabilidad de eventos críticos con NestJS y MongoDB. También evolucioné APIs, investigué problemas de seguridad y contribuí a jornadas digitales y componentes web cuando era necesaria la continuidad entre backend e interfaz.\n\nEl principio que orientó mi trabajo fue hacer observables y tratables las fallas desde el diseño. En mensajería, traté durabilidad, retry, idempotencia y control de consumo como partes del flujo, no como correcciones posteriores.\n\n## Lo que me llevé\n\nEsta experiencia amplió mi trabajo en sistemas con muchas dependencias y especialistas. Aprendí a convertir requisitos, riesgos y restricciones operativas en decisiones que siguieran claras hasta la validación con el cliente.\n\n## Caso relacionado\n\nEl caso detalla cómo rediseñé la mensajería RabbitMQ para hacer más previsibles los flujos críticos y reducir fallas intermitentes entre microservicios.\n\n[Leer el caso completo](/es/casos/mensajeria-rabbitmq/)",
      "description": "Mi experiencia en Infosistemas con mensajería, integraciones y plataformas de movilidad.",
      "keywords": [
        "infosistemas",
        "mensajería",
        "integraciones",
        "trabajé",
        "flujos",
        "trabajo",
        "entre",
        "caso",
        "experiencia",
        "plataformas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/infosistemas"
      }
    },
    {
      "id": "a776a6a554aaccee",
      "url": "https://imrafaeldev.site/es/experiencias/south-system-quiq-itau",
      "title": "South System, QUIQ e Itaú",
      "content": "- [Inicio](/es/)\n- South System (asignado a QUIQ/Itaú)\n\n## South System (asignado a QUIQ/Itaú)\n\nMi experiencia con un marketplace white-label y multi-tenant para instituciones financieras.\n\nCargo\n\nIngeniero Backend\n\nPeríodo\n\njun/2022 a abr/2023\n\n## Contexto\n\nEn South System, fui asignado a QUIQ para trabajar en un Marketplace as a Service para instituciones financieras. Itaú fue el primer contexto, pero el producto necesitaba incorporar nuevos bancos sin requerir un fork por cliente.\n\n## Cómo trabajé\n\nParticipé en arquitectura, modelado de datos, elección de tecnologías y refinamiento de reglas con producto. El resultado fue una plataforma white-label y multi-tenant, con aislamiento lógico entre tenants y Arquitectura Hexagonal para separar el dominio de las integraciones específicas.\n\nTrabajé con Node.js, TypeScript, MySQL, servicios asíncronos en Go y AWS. Estructuré pruebas unitarias y de integración para casos críticos y usé análisis estático como parte del flujo de calidad.\n\n## Lo que me llevé\n\nEsta experiencia cambió cómo comunico arquitectura. Pasé a tratar la alineación con producto, Product Owner y stakeholders como parte de la decisión técnica, no como una etapa posterior al código.",
      "description": "Mi experiencia con un marketplace white-label y multi-tenant para instituciones financieras.",
      "keywords": [
        "para",
        "south",
        "system",
        "asignado",
        "quiq",
        "itaú",
        "producto",
        "arquitectura",
        "como",
        "experiencia"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/south-system-quiq-itau"
      }
    },
    {
      "id": "a801fd3f3778f366",
      "url": "https://imrafaeldev.site/artigos/typescript-cleanarch",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 3)",
      "content": "if (existsName) {\nthrow new UserAlreadyExistsException(\n`the name ${name} already exists`,\n);\n}\n\nreturn this.createUserProtocol.register(name);\n}\n}\nO usecase define *o quê* (validar nome, registrar). Não define *como* buscar ou persistir. A regra fica independente de lib, framework e banco.\n\nCuidado: usecase que só delega ao protocol sem validar pode estar empurrando regra de negócio para o adapter. Em CreateUserUsecase, a checagem de nome duplicado é obrigação do Core.\n\n### Exceptions\n\nUserAlreadyExistsException pertence ao Core: fluxo inválido da regra também é regra. Cada falha mapeada a uma exceção conhecida ajuda manutenção. Base com code (depois vira status HTTP na borda):\n\n// core/exceptions/IBaseException.ts\nexport abstract class IBaseException extends Error {\ncode: number;\n\nconstructor(message: string) {\nsuper(message);\n}\n}\n// core/exceptions/UserAlreadyExistsException.ts\nexport class UserAlreadyExistsException extends IBaseException {\nconstructor(message?: string) {\nsuper(message ?? \"User already exists\");\nthis.code = 400;\n}\n}\nO usecase **lança** exceções; **não** as trata. Mapear tipo desconhecido → tipo conhecido fica em adapter ou infra.\n\n### Protocols\n\nCreateUserProtocol e GetUserByNameProtocol são contratos de acesso a dispositivo externo. Protocol existe para informar ou disparar ação externa — **não** para processar regra de negócio. Preferência: um método público por protocol.\n\n// core/protocols/CreateUserProtocol.ts\nexport abstract class CreateUserProtocol {\nabstract register(name: string): Promise&#x3C;UserEntity>;\n}\n// core/protocols/GetUserByNameProtocol.ts\nexport abstract class GetUserByNameProtocol {\nabstract getByName(name: string): Promise&#x3C;UserEntity | null>;\n}\nO Core é o centro; a camada de adaptação liga o resto.\n\n## Adapter\n\nAdapters controlam o tráfego bidirecional: externo → regra e regra → externo. Adaptam objetos, parâmetros e exceções — o mesmo espírito do [padrão Adapter](/artigos/design-patterns-adapter/).\n\nDois grupos:",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "name",
        "string",
        "core",
        "promise",
        "userentity",
        "async",
        "para",
        "this",
        "regra",
        "export"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/artigos/typescript-cleanarch"
      }
    },
    {
      "id": "a80a77f2cf5bdbca",
      "url": "https://imrafaeldev.site/fundacao",
      "title": "Verificação da fundação",
      "content": "## Página sem variantes em inglês ou espanhol\n\nO seletor de idioma deve levar à home do idioma escolhido, não a um fallback em português.\n\nEsta rota existe só para verificar a fundação multilíngue. Não faz parte da navegação pública nem do sitemap.",
      "description": "Página temporária, sem índice, usada para validar a troca de idioma quando não há variante publicada.",
      "keywords": [
        "idioma",
        "não",
        "página",
        "variantes",
        "inglês",
        "espanhol",
        "seletor",
        "deve",
        "levar",
        "home"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/fundacao"
      }
    },
    {
      "id": "a896b5f08f5e2b58",
      "url": "https://imrafaeldev.site/experiencias/south-system-quiq-itau",
      "title": "South System, QUIQ e Itaú",
      "content": "- [Início](/)\n- South System (alocado na QUIQ/Itaú)\n\n## South System (alocado na QUIQ/Itaú)\n\nMinha experiência com marketplace white-label e multi-tenant para instituições financeiras.\n\nCargo\n\nEngenheiro Backend\n\nPeríodo\n\njun/2022 a abr/2023\n\n## O contexto\n\nNa South System, fui alocado na QUIQ para trabalhar em um Marketplace as a Service voltado a instituições financeiras. O primeiro contexto era o Itaú, mas o produto precisava receber novos bancos sem exigir um fork por cliente.\n\n## Como atuei\n\nParticipei do desenho da arquitetura, da modelagem de banco, da escolha de tecnologias e do refinamento das regras com produto. A solução foi construída como uma plataforma white-label e multi-tenant, com isolamento lógico entre tenants e Arquitetura Hexagonal para manter o domínio separado das integrações específicas.\n\nTrabalhei com Node.js, TypeScript, MySQL, serviços assíncronos em Go e AWS. Estruturei testes unitários e de integração para os casos críticos e usei análise estática como parte do fluxo de qualidade.\n\n## O que levo\n\nEssa experiência mudou minha forma de comunicar arquitetura. Passei a tratar alinhamento com produto, PO e stakeholders como parte da decisão técnica, não como uma etapa posterior ao código.",
      "description": "Minha experiência com marketplace white-label e multi-tenant para instituições financeiras.",
      "keywords": [
        "como",
        "para",
        "south",
        "system",
        "alocado",
        "quiq",
        "itaú",
        "produto",
        "arquitetura",
        "minha"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/south-system-quiq-itau"
      }
    },
    {
      "id": "a8973367df5b562f",
      "url": "https://imrafaeldev.site/projetos",
      "title": "Projetos (Part 2)",
      "content": "A entrevista adaptativa extrai evidências; o gateway híbrido (LLM + heurística) barra vivência inventada. Apenas conteúdo confirmado segue para rascunho e exportação em Markdown ou SlideMark.\n\n[Abrir o projeto](/projetos/post-engine/)\n\nAutoria antes da geração\nEntrevista extrai evidência; briefing e storyboard preparam o material; o gateway veta fabricado. Só o confirmado segue para rascunho e export.\n\n## Outros trabalhos, outros contextos\n\nProjetos visuais para explorar manualmente.\n\n-\n[Gran Goiás Site institucional da Gran Goiás, marmoraria com execução em pedra para obras de escala. Visitar site](https://gran-goias.vercel.app/)\n\n-\n[Gabriel | Nutrição Esportiva Site institucional de Gabriel Pereira para nutrição esportiva, com conteúdo real e estratég](https://gabriel-pereira-nutri.vercel.app/)",
      "description": "Artefatos com problema, restrições e estado atual do repositório.",
      "keywords": [
        "projetos",
        "não",
        "abrir",
        "projeto",
        "para",
        "sqlite",
        "local",
        "revisão",
        "diffvision",
        "estado"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/projetos"
      }
    },
    {
      "id": "a8fb5427ae18b0e7",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 1)",
      "content": "Uma API fica lenta e a reunião já enche de soluções antes de ganhar uma medição. Aparecem mais CPU e RAM, cache, fila, microserviços e refatoração. Às vezes alguém propõe até trocar a linguagem. Raramente a primeira sugestão é abrir o plano de execução.\n\nPor isso, começo a investigação perto dos dados. Não porque o banco seja o culpado por padrão, mas porque muita coisa passa por ali e essa verificação costuma dar sinal rápido. Se a consulta estiver saudável, tiro o banco da frente e sigo o fluxo da requisição.\n\n## Soluções rápidas também escondem trabalho caro\n\nCache e fila resolvem problemas reais. Mais máquina também pode ser a decisão certa. Quando entram por reflexo, porém, esses recursos podem apenas mudar o tamanho da conta enquanto ninguém sabe qual trecho da requisição está segurando a resposta.\n\nSubir CPU reduz a disputa por recurso, cache tira algumas requisições do caminho e fila absorve um pico. Enquanto isso, uma consulta que faz um trabalho enorme para retornar quase nada continua cara em cada execução. O mesmo vale para um loop que dispara uma chamada por item.\n\nA latência pode cair por um tempo, o alerta para de tocar e o time respira. Quando a carga cresce, a operação cara reaparece, agora acompanhada de uma infraestrutura maior.\n\nA pergunta que precisa vir antes da discussão de arquitetura: quanto trabalho essa requisição está produzindo para entregar o resultado?\n\n## Quase dobramos a máquina e o dashboard continuou lento\n\nFoi o que aconteceu em um dashboard em que trabalhei. Ele carregava de forma síncrona, e uma única consulta fazia tudo de uma vez: buscava os dados, cruzava várias informações e calculava os valores exibidos. Como a CPU do banco ficava muito alta, quase dobramos CPU e RAM.\n\nO banco ficou maior. O dashboard continuou passando de um minuto para carregar. A mesma consulta ainda concentrava todo o trabalho pesado em uma única execução.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "a8ffc41bc01d2be6",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-en",
      "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern (Part 1)",
      "content": "With the Adapter pattern, we isolate the business rule from the concrete dependency.\n\n## Starting scenario\n\nWe have a backend with a simple user CRUD: create, edit, retrieve, and delete through API endpoints. Data goes to some database — say MySQL — and the structure starts as **Controller → Service → Database**. So far, the team is comfortable.\n\nUntil someone decides: next week we migrate from MySQL to PostgreSQL. From there, the house falls down on technology. Beyond restructuring the database, the team must hunt down MySQL references: inserts, connection, scattered queries.\n\nMost of the time this delays delivery, reduces quality, skips tests, and introduces bugs. It would be better to reduce the dependency between the business rule and whoever performs the specific operation — here, the database. The Adapter helps build the system that way.\n\n## Adapter pattern\n\nWe have a third-party plugin, library, module, or service that does something we want in the business rule — here, persisting data. The path:\n\n1. Define an interface with the contract of what we need.\n2. Expose only methods that make sense in context (SOLID).\n3. Implement the interface in classes that adapt the third-party code.\n\nWe create `CreateDatabaseCustomerProtocol` with a `create` method that receives `CustomerInputEntity` and returns `SuccessfulEntityCreation`:\n\n```typescript\ninterface CustomerInputEntity {\n  name: string;\n  email: string;\n  birthDate: Date;\n}\n\ninterface SuccessfulEntityCreation {\n  readonly id: number;\n  readonly name: string;\n  readonly email: string;\n  readonly birthDate: Date;\n}\n\ninterface CreateDatabaseCustomerProtocol {\n  createCustomerOnDatabase(\n    customer: CustomerInputEntity,\n  ): SuccessfulEntityCreation;\n}\n```\n\nInstead of **Controller → Service → Database**, we move to **Controller → Service → Protocols → Plugin**.",
      "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
      "keywords": [
        "adapter",
        "service",
        "database",
        "business",
        "rule",
        "that",
        "interface",
        "with",
        "readonly",
        "dependency"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern",
        "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
        "excerpt": "Migrating from MySQL to PostgreSQL does not require rewriting the service. The Adapter isolates the plugin behind a contract the business rule understands.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-en.md"
      }
    },
    {
      "id": "a9d993b1017fdb8b",
      "url": "https://imrafaeldev.site/contato",
      "title": "Contato",
      "content": "- [Início](/)\n- Contato\n\nContato\n\n## Tem um sistema que deixou de ser simples?\n\nChame direto pelo @imrafaeldev, sem formulário. Para conversa profissional, comece pelo LinkedIn; para ver decisões em código, vá ao GitHub.\n\n- [Instagram @imrafaeldev](https://www.instagram.com/imrafaeldev/)\n- [YouTube @imrafaeldev](https://www.youtube.com/@imrafaeldev)\n- [GitHub @imrafaeldev](https://github.com/imrafaeldev)\n- [LinkedIn @imrafaeldev](https://www.linkedin.com/in/imrafaeldev/)",
      "description": "Fale com Rafael Pereira pelo @imrafaeldev no LinkedIn, GitHub, Instagram e YouTube. Sem formulário: escolha o canal e chame direto.",
      "keywords": [
        "imrafaeldev",
        "https",
        "linkedin",
        "github",
        "contato",
        "pelo",
        "para",
        "instagram",
        "youtube",
        "início"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/contato"
      }
    },
    {
      "id": "ac149d7dd2891dd3",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 2)",
      "content": "```typescript\n// core/entities/UserEntity.ts\nexport interface UserEntityProps {\n  id?: string;\n  name: string;\n}\n\nexport class UserEntity {\n  constructor(private readonly props: UserEntityProps) {}\n\n  get id(): string {\n    return this.props.id ?? \"\";\n  }\n\n  get name(): string {\n    return this.props.name;\n  }\n}\n```\n\nA entidade recebe props tipadas e expõe getters. Depende de uma interface que qualquer DTO de transferência pode satisfazer depois.\n\n### Features e usecases\n\nO CRUD precisa criar, buscar, atualizar e remover. No Core, cada usecase implementa um contrato (feature) com um único método público — alinhado a Liskov, aberto/fechado, segregação de interface e responsabilidade única. O usecase **não** acessa o banco: conhece **protocols** que descrevem a ação externa (inversão de dependência).\n\nCadastro: nome obrigatório; se já existir, erro; se não, retorna `UserEntity`.\n\n- contrato `CreateUser`\n- implementação `CreateUserUsecase`\n\nNo TypeScript, classe abstrata com métodos abstratos funciona como contrato *e* valor — útil para DI (`const createUserSymbol = CreateUser`):\n\n```typescript\n// core/features/CreateUser.ts\nexport abstract class CreateUser {\n  abstract execute(name: string): Promise<UserEntity>;\n}\n```\n\n```typescript\n// core/usecases/CreateUserUsecase.ts\nexport class CreateUserUsecase implements CreateUser {\n  constructor(\n    private readonly createUserProtocol: CreateUserProtocol,\n    private readonly getByNameProtocol: GetUserByNameProtocol,\n  ) {}\n\n  async execute(name: string): Promise<UserEntity> {\n    const existsName = await this.getByNameProtocol.getByName(name);\n\n    if (existsName) {\n      throw new UserAlreadyExistsException(\n        `the name ${name} already exists`,\n      );\n    }\n\n    return this.createUserProtocol.register(name);\n  }\n}\n```\n\nO usecase define *o quê* (validar nome, registrar). Não define *como* buscar ou persistir. A regra fica independente de lib, framework e banco.",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "ac8f3eec7da75aca",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-es",
      "title": "Design Patterns: Strategy (Part 3)",
      "content": "El contexto recibe y ejecuta la estrategia. Conoce solo el contrato, no la implementación concreta. La antigua `DefaultCalculator` pasa a recibir ese `ContextAnalyzer` por inyección:\n\n```typescript\nclass Calculator {\n  constructor(private readonly contextAnalyzer: ContextAnalyzer) {}\n\n  /**\n   * La implementación de calculate en Calculator no cambia por operación.\n   * Lo que crece es el ContextAnalyzer, que añade un case por operación nueva.\n   */\n  public calculate(parameters: BinaryOperationParameters): Result {\n    const { operator, firstOperand, secondOperand } = parameters;\n    return this.contextAnalyzer\n      .getInstance(operator)\n      .calculate({ firstOperand, secondOperand });\n  }\n}\n```\n\n## ¿Por qué usar Strategy?\n\nStrategy ayuda en legado con varias reglas de negocio, cada una representada por un `if` y una implementación extensa. La parte común queda en el contrato; cada variación de regla queda en su propia clase; un analizador de contexto (resolver/factory) elige la estrategia.\n\nEn cada petición, el código evalúa el contexto de la operación y selecciona la implementación correspondiente al contrato.\n\n## Relación con otros patrones\n\n- Adapter: Strategy varía comportamiento; Adapter aísla dependencias externas tras una interfaz propia.\n- SOLID (OCP): Strategy es una forma de aplicar el Open/Closed Principle.",
      "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "parameters",
        "operator",
        "strategy",
        "cada",
        "operación",
        "calculate"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
        "excerpt": "Las cadenas de if/else crecen y se vuelven frágiles. Strategy aísla cada algoritmo tras un contrato.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-es.md"
      }
    },
    {
      "id": "acbbd0b0222e64f3",
      "url": "https://imrafaeldev.site/google59795d012238450d",
      "title": "/google59795d012238450d",
      "content": "google-site-verification: google59795d012238450d.html",
      "keywords": [
        "google-site-verification",
        "google59795d012238450d",
        "html"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/google59795d012238450d"
      }
    },
    {
      "id": "acdaee5fa3043f70",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-es",
      "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia (Part 4)",
      "content": "Empezaría a mirar hacia Go cuando la tarea fuera claramente CPU bound: cálculo en alto volumen de datos, procesamiento de imagen en lote, agregaciones pesadas, compresión, transformación grande de buffers, simulaciones o cualquier rutina en que la máquina pase más tiempo calculando que esperando respuesta externa.\n\nEn ese tipo de escenario, las goroutines con worker pool dan mejor control sobre el uso de CPU. También dejan más explícita la separación entre unidades de trabajo, sincronización y recolección de resultado.\n\nPero Go también cobra precio. Hay que pensar en granularidad de los chunks, consumo de memoria, cancelación, tratamiento de error, backpressure, límites de workers, contención y consistencia del resultado.\n\nSi la división del trabajo es ingenua, la ganancia de CPU puede venir acompañada de estallido de memoria o complejidad innecesaria.\n\n## Contraargumento: Node.js también tiene worker threads\n\nExiste un contraargumento justo: Node.js no está limitado al Event Loop para todo. Los worker threads existen justamente para ejecutar trabajo pesado fuera del hilo principal. También hay estrategias con colas, procesos separados, servicios auxiliares y native addons.\n\nEntonces la comparación honesta no es \"Node.js no puede\". Puede.\n\nLa cuestión es costo de implementación, madurez del equipo, observabilidad, integración con el sistema existente y cuánto esfuerzo vale invertir para mantener ese procesamiento dentro del ecosistema Node.\n\nEn algunos equipos, usar worker threads puede bastar y ser más barato que introducir Go. En otros, separar el procesamiento CPU bound en un servicio Go puede ser más simple de operar y escalar.\n\nLa decisión no debería nacer de preferencia por lenguaje. Debería nacer de la naturaleza de la carga.\n\n## La regla práctica que quedó",
      "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
      "keywords": [
        "node",
        "puede",
        "más",
        "tiempo",
        "mismo",
        "trabajo",
        "loop",
        "problema",
        "para",
        "resultado"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: la comparación equivocada entre dos modelos de concurrencia",
        "description": "Concurrencia no es paralelismo. Cuándo el Event Loop de Node.js basta para I/O y cuándo las goroutines en Go encajan mejor en carga CPU bound.",
        "excerpt": "Node.js con Event Loop y Go con goroutines no resuelven el mismo problema del mismo modo. El error común es confundir concurrencia con paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-es.md"
      }
    },
    {
      "id": "ace18718df25a883",
      "url": "https://imrafaeldev.site/es/articulos/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 2)",
      "content": "Beneficios concretos: responsabilidades claras (lectura y mantenimiento), flexibilidad para cambiar plugin sin reescribir regla, y pruebas aisladas por capa.\n\n## Guía de capas\n\nEjemplo: CRUD de usuarios vía REST con NestJS. Detalles de instalación quedan fuera. Escritura **core-to-infra** (de dentro hacia fuera).\n\n## Core\n\nEn el diseño clásico, *domain* y *entities* quedan muy próximas. Aquí forman el **Core**: todo lo que la regla de negocio *es* — funcionalidades y representaciones del dominio.\n\nEn el ejemplo, la entidad principal es Usuario (id, name), en core/entities.\n\n### Entities\n\n// core/entities/UserEntity.ts\nexport interface UserEntityProps {\nid?: string;\nname: string;\n}\n\nexport class UserEntity {\nconstructor(private readonly props: UserEntityProps) {}\n\nget id(): string {\nreturn this.props.id ?? \"\";\n}\n\nget name(): string {\nreturn this.props.name;\n}\n}\nLa entidad recibe props tipadas y expone getters. Depende de una interfaz que cualquier DTO de transferencia puede satisfacer después.\n\n### Features y usecases\n\nEl CRUD necesita crear, buscar, actualizar y eliminar. En el Core, cada usecase implementa un contrato (feature) con un único método público — alineado a Liskov, abierto/cerrado, segregación de interfaz y responsabilidad única. El usecase **no** accede a la base: conoce **protocols** que describen la acción externa (inversión de dependencia).\n\nRegistro: nombre obligatorio; si ya existe, error; si no, retorna UserEntity.\n\n- contrato CreateUser\n\n- implementación CreateUserUsecase\n\nEn TypeScript, clase abstracta con métodos abstractos funciona como contrato *y* valor — útil para DI (const createUserSymbol = CreateUser):",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "regla",
        "export",
        "architecture",
        "para",
        "class"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/typescript-clean-architecture"
      }
    },
    {
      "id": "adea3dafcb98fbe1",
      "url": "https://imrafaeldev.site/articles/go-intensivo-es",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 3)",
      "content": "Usa `atomic` para un contador o flag independiente, `sync.Mutex` para una invariante entre campos y channel para transferir trabajo. No copies un mutex después del primer uso ni mantengas un lock durante I/O remoto.\n\nBackpressure es una política de producto y operación. Si la ingesta recibe 50 mil mensajes por segundo y la persistencia completa 20 mil, guardar el resto en memoria solo desplaza el incidente. Decide si bloquear productor, rechazar con retry, pausar consumo para que el broker retenga backlog durable, descartar muestras antiguas, agregar datos o persistir en disco. Define tamaño de cola, métrica de ocupación, timeout y acción de saturación.\n\n## Pipeline IoT resistente a reentregas\n\n```text\ndispositivo -> MQTT/broker -> ingesta Go -> stream -> procesadores -> almacenamiento\n                                \\-> DLQ        \\-> estado actual\n```\n\nMQTT encaja en la conectividad de dispositivos. Un stream como Kafka encaja en retención durable, replay y particionamiento interno. gRPC es RPC interno tipado; WebSocket actualiza dashboards. Resuelven fronteras distintas.\n\nVarios workers rompen el orden global. Telemetría suele necesitar orden por dispositivo, así que particiona por una clave estable como `hash(device_id) % N` y procesa cada partición de forma secuencial. Guarda `observed_at`, `ingested_at`, `sequence`, `event_id` y `boot_id` cuando exista. El reloj del dispositivo puede desfasarse o reiniciarse.\n\nDiseña la cadena para entrega *at least once*. Recibe el evento, valida envelope y versión del schema, comprueba la clave de idempotencia, persiste efecto y marcador de deduplicación en la misma transacción cuando sea posible y recién entonces hace ACK. Una transactional outbox cierra la ventana entre confirmar el estado en base y publicar el evento siguiente. El consumer sigue necesitando idempotencia porque las duplicatas pueden ocurrir.",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "orden",
        "channel",
        "context",
        "https",
        "return",
        "cada",
        "más",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-go-intensive",
        "slug": "go-intensivo",
        "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos",
        "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
        "excerpt": "Una guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 6,
        "sourcePath": "articles/go-intensivo-es.md"
      }
    },
    {
      "id": "aef89fc47141b063",
      "url": "https://imrafaeldev.site/en/articles/design-patterns-adapter",
      "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Stop being hostage to dependencies. Say hello to the Adapter design pattern\n\n## Stop being hostage to dependencies. Say hello to the Adapter design pattern\n\nMigrating from MySQL to PostgreSQL does not require rewriting the service. The Adapter isolates the plugin behind a contract the business rule understands.\n\nApril 26, 2022\n\n- [Architecture](/en/articles/?topic=arquitetura)\n\nWith the Adapter pattern, we isolate the business rule from the concrete dependency.\n\n## Starting scenario\n\nWe have a backend with a simple user CRUD: create, edit, retrieve, and delete through API endpoints. Data goes to some database — say MySQL — and the structure starts as **Controller → Service → Database**. So far, the team is comfortable.\n\nUntil someone decides: next week we migrate from MySQL to PostgreSQL. From there, the house falls down on technology. Beyond restructuring the database, the team must hunt down MySQL references: inserts, connection, scattered queries.\n\nMost of the time this delays delivery, reduces quality, skips tests, and introduces bugs. It would be better to reduce the dependency between the business rule and whoever performs the specific operation — here, the database. The Adapter helps build the system that way.\n\n## Adapter pattern\n\nWe have a third-party plugin, library, module, or service that does something we want in the business rule — here, persisting data. The path:\n\n- Define an interface with the contract of what we need.\n\n- Expose only methods that make sense in context (SOLID).\n\n- Implement the interface in classes that adapt the third-party code.\n\nWe create CreateDatabaseCustomerProtocol with a create method that receives CustomerInputEntity and returns SuccessfulEntityCreation:\n\ninterface CustomerInputEntity {\nname: string;\nemail: string;\nbirthDate: Date;\n}\n\ninterface SuccessfulEntityCreation {\nreadonly id: number;\nreadonly name: string;\nreadonly email: string;\nreadonly birthDate: Date;\n}",
      "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
      "keywords": [
        "adapter",
        "service",
        "business",
        "rule",
        "database",
        "that",
        "interface",
        "mysql",
        "does",
        "plugin"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/en/articles/design-patterns-adapter"
      }
    },
    {
      "id": "af3e5eca3933bca7",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 8)",
      "content": "// core/usecases/GetUserUsecase.ts\nexport class GetUserUsecase implements GetUser {\n  constructor(private readonly getProtocol: GetUserByIdProtocol) {}\n\n  async execute(id: string): Promise<UserEntity> {\n    return this.getProtocol.getById(id);\n  }\n}\n```\n\n### Atualização\n\n```typescript\n// core/features/UpdateUser.ts\nexport abstract class UpdateUser {\n  abstract execute(id: string, name: string): Promise<UserEntity>;\n}\n\n// core/usecases/UpdateUserUsecase.ts\nexport class UpdateUserUsecase implements UpdateUser {\n  constructor(\n    private readonly updateProtocol: UpdateUserProtocol,\n    private readonly getByIdProtocol: GetUserByIdProtocol,\n  ) {}\n\n  async execute(id: string, name: string): Promise<UserEntity> {\n    const exists = await this.getByIdProtocol.getById(id);\n\n    if (!exists) {\n      throw new UserNotExistsException(`User with id ${id} not exists`);\n    }\n\n    return this.updateProtocol.update(id, name);\n  }\n}\n```\n\n### Deleção\n\n```typescript\n// core/features/DeleteUser.ts\nexport abstract class DeleteUser {\n  abstract execute(id: string): Promise<void>;\n}\n\n// core/usecases/DeleteUserUsecase.ts\nexport class DeleteUserUsecase implements DeleteUser {\n  constructor(\n    private readonly deleteProtocol: DeleteUserProtocol,\n    private readonly getByIdProtocol: GetUserByIdProtocol,\n  ) {}\n\n  async execute(id: string): Promise<void> {\n    const exists = await this.getByIdProtocol.getById(id);\n\n    if (!exists) {\n      throw new UserNotExistsException(`User with id ${id} not exists`);\n    }\n\n    return this.deleteProtocol.delete(id);\n  }\n}\n```\n\n### Exception e protocols restantes\n\n```typescript\nexport class UserNotExistsException extends IBaseException {\n  constructor(message: string) {\n    super(message);\n    this.code = 404;\n  }\n}\n```\n\n```typescript\n// core/protocols/GetUserByIdProtocol.ts\nexport abstract class GetUserByIdProtocol {\n  abstract getById(id: string): Promise<UserEntity>;\n}",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 7,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "af4d042201cad6e1",
      "url": "https://imrafaeldev.site/es/experiencias/sustentec",
      "title": "Sustentec",
      "content": "- [Inicio](/es/)\n- Sustentec\n\n## Sustentec\n\nMi experiencia en Sustentec con sistemas de investigación, APIs y calidad.\n\nCargo\n\nIngeniero de Software Full Stack Pleno\n\nPeríodo\n\nene/2021 a ago/2021\n\n## Contexto\n\nEn Sustentec, trabajé en sistemas relacionados con laboratorios, investigación y desarrollo. La experiencia combinó el mantenimiento de un producto existente con la evolución de funcionalidades e integraciones.\n\n## Cómo trabajé\n\nDesarrollé una API REST en Dart con Shelf para integrar bases de instituciones de investigación. También mantuve y evolucioné un sistema de gestión de laboratorios con Java, Spring Boot, JPA, Hibernate, PostgreSQL y Angular.\n\nImplementé pruebas de integración donde antes no existía esa cobertura, desarrollé informes y funcionalidades de extremo a extremo y participé en la recopilación de requisitos con clientes y el refinamiento de sprints con el Product Owner.\n\n## Lo que me llevé\n\nEsta experiencia reforzó que la calidad no se limita a pruebas unitarias. En un sistema con varias capas, necesitaba validar el comportamiento real entre API, persistencia e interfaz.",
      "description": "Mi experiencia en Sustentec con sistemas de investigación, APIs y calidad.",
      "keywords": [
        "sustentec",
        "experiencia",
        "investigación",
        "2021",
        "sistemas",
        "calidad",
        "trabajé",
        "laboratorios",
        "funcionalidades",
        "desarrollé"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/sustentec"
      }
    },
    {
      "id": "aff4b2daaaa1ae17",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 6)",
      "content": "```typescript\nexport class UserService {\n  constructor(\n    private createUserUsecase: CreateUser,\n    private updateUserUsecase: UpdateUser,\n    private deleteUserUsecase: DeleteUser,\n    private getUserUsecase: GetUser,\n  ) {}\n\n  async getUser(id: string): Promise<UserEntity> {\n    try {\n      return await this.getUserUsecase.execute(id);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async createUser(name: string): Promise<UserEntity> {\n    try {\n      return await this.createUserUsecase.execute(name);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async updateUser(id: string, name: string): Promise<UserEntity> {\n    try {\n      return await this.updateUserUsecase.execute(id, name);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n\n  async deleteUser(id: string): Promise<void> {\n    try {\n      return await this.deleteUserUsecase.execute(id);\n    } catch (error) {\n      console.error(error);\n      throw RestError.fromBaseException(error);\n    }\n  }\n}\n```\n\nThe service depends on **features** (contracts), not concrete usecase classes. It maps Core exceptions to `RestError`, which Infra translates into HTTP responses.\n\nGood practice: services only for Infra; one service per input \"instrument\" (HTTP controller ≠ queue handler), except middleware in the same stack reusing the same service.\n\n## Infra\n\nFramework, DI, controllers, DTOs, and boilerplate that is neither business nor adapter.\n\nNestJS controller:\n\n```typescript\n// infra/controllers/UserController.ts\n@Controller(\"users\")\nexport class UserController {\n  constructor(private service: UserService) {}\n\n  @Get(\":id\")\n  async getUser(@Param(\"id\") id: string): Promise<UserEntity> {\n    return this.service.getUser(id);\n  }",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "b0bfdcba3597f3b5",
      "url": "https://imrafaeldev.site/es/experiencias/vbet",
      "title": "VBET",
      "content": "- [Inicio](/es/)\n- VBET\n\n## VBET\n\nMi experiencia en VBET con seguridad, analítica y rendimiento a escala.\n\nCargo\n\nIngeniero Backend Senior\n\nPeríodo\n\noct/2023 a feb/2025\n\n## Contexto\n\nEn VBET, trabajé en un producto de analítica para afiliados e influencers de iGaming. El sistema calculaba métricas financieras y operativas en una plataforma que empezó a atender audiencias mucho mayores de lo previsto originalmente.\n\n## Cómo trabajé\n\nAntes de abordar rendimiento, reduje riesgos de seguridad y mantenimiento en la API legada. Reemplacé consultas inseguras, organicé la base con Clean Architecture e inyección de dependencias y establecí pruebas y documentación para sostener los cambios siguientes.\n\nDespués traté el rendimiento por etapas. Usé Go, goroutines, channels y consultas paralelas para reducir la primera etapa del cálculo de comisiones de cerca de siete a tres minutos. Como el SQL Server era externo y no podía cambiarse, diseñé un ETL con checkpoints, agregaciones precalculadas y reconciliación, diferenciando datos provisionales de datos consolidados.\n\n## Lo que me llevé\n\nEsta experiencia consolidó cómo tomo decisiones de rendimiento: entender la restricción real, aceptar la consistencia adecuada para cada uso y después elegir la tecnología que la resuelve.\n\n## Caso relacionado\n\nEl caso profundiza en la evolución del dashboard de analítica: desde la reducción de riesgos en la API hasta ETL, precálculo, reconciliación y caché sobre un SQL Server externo.\n\n[Leer el caso completo](/es/casos/analitica-sql-server-externo/)",
      "description": "Mi experiencia en VBET con seguridad, analítica y rendimiento a escala.",
      "keywords": [
        "vbet",
        "rendimiento",
        "para",
        "analítica",
        "caso",
        "experiencia",
        "seguridad",
        "trabajé",
        "cómo",
        "riesgos"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/vbet"
      }
    },
    {
      "id": "b151b0fee7dcee9a",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 1)",
      "content": "Desenvolvimento de software muda o tempo todo. Arquitetura fraca vira manutenção cara, feature lenta, teste difícil e bug difícil de isolar. Vale investir em uma estrutura que suporte evolução sem reescrever o sistema a cada pressão do negócio.\n\n## Um pouco de história\n\nClean Architecture é o nome que Robert C. Martin (Uncle Bob) deu, em 2012, no livro *Clean Architecture: A Craftsman's Guide to Software Structure and Design*. A proposta foge da rigidez de arquiteturas acopladas a framework e banco: o núcleo fica estável; detalhes externos mudam. A ideia bebe de DDD, SOLID, Onion Architecture e Hexagonal Architecture.\n\n## Proposta geral\n\nEste artigo descreve a Clean Architecture e uma derivação prática para backends em TypeScript: três camadas — **Core**, **Adapters** e **Infra**.\n\n- **Core** — regra de negócio e entidades do domínio. Camada mais interna.\n- **Infra** — conexões externas: repositórios concretos, controllers REST, módulos de DI, boilerplate de framework.\n- **Adapters** — intermediação nos dois sentidos. Controller não chama usecase “cru”: passa por um serviço. Usecase não fala com o banco: fala com um protocolo que um adapter (repositório, connector, handler) implementa.\n\nCada camada tem capacidades e restrições diferentes; SOLID pesa mais no Core. Serve para CRUD HTTP e para sistemas com vários frameworks e canais.\n\nBenefícios concretos: responsabilidades claras (leitura e manutenção), flexibilidade para trocar plugin sem reescrever regra, e testes isolados por camada.\n\n## Guia de camadas\n\nExemplo: CRUD de usuários via REST com NestJS. Detalhes de instalação ficam de fora. Escrita **core-to-infra** (de dentro para fora).\n\n## Core\n\nNo desenho clássico, *domain* e *entities* ficam muito próximas. Aqui elas formam o **Core**: tudo o que a regra de negócio *é* — funcionalidades e representações do domínio.\n\nNo exemplo, a entidade principal é Usuário (`id`, `name`), em `core/entities`.\n\n### Entities",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "b18971a428925af6",
      "url": "https://imrafaeldev.site/experiences/sustentec-en",
      "title": "Sustentec | Rafael Pereira",
      "content": "## Context\n\nAt Sustentec, I worked on systems connected to laboratories, research, and development. The work combined maintaining an existing product with evolving its features and integrations.\n\n## How I worked\n\nI developed a REST API in Dart with Shelf to integrate research-institution databases. I also maintained and evolved a laboratory management system with Java, Spring Boot, JPA, Hibernate, PostgreSQL, and Angular.\n\nI added integration tests where that coverage did not exist, developed reports and end-to-end features, and took part in gathering requirements with clients and refining sprints with the Product Owner.\n\n## What I took from it\n\nThis experience reinforced that quality is not limited to unit tests. In a system with several layers, I needed to validate the actual behavior across API, persistence, and interface.",
      "description": "My experience at Sustentec with research systems, APIs, and quality.",
      "keywords": [
        "with",
        "worked",
        "product",
        "features",
        "developed",
        "system",
        "tests",
        "that",
        "took",
        "context"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-sustentec",
        "slug": "sustentec",
        "title": "Sustentec | Rafael Pereira",
        "description": "My experience at Sustentec with research systems, APIs, and quality.",
        "company": "Sustentec",
        "role": "Mid-level Full Stack Software Engineer",
        "period": "Jan 2021 to Aug 2021",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/sustentec-en.md"
      }
    },
    {
      "id": "b1b96b88c1473a4c",
      "url": "https://imrafaeldev.site/artigos/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Design Patterns: Strategy\n\n## Design Patterns: Strategy\n\nCadeias de if/else crescem e ficam frágeis. O Strategy isola cada algoritmo atrás de um contrato.\n\n5 de maio de 2022\n\n- [Arquitetura](/artigos/?topic=arquitetura)\n\nCadeias de if/else crescem e ficam frágeis. O padrão Strategy encapsula cada algoritmo em sua própria classe e permite trocar implementações em tempo de execução sem alterar o código que as consome.\n\n## O problema: if/else infinito\n\nUma calculadora com soma, subtração, multiplicação e divisão costuma nascer como uma classe DefaultCalculator: métodos privados por operação e uma função pública que escolhe qual invocar com switch ou cadeia de if/else.\n\nclass DefaultCalculator {\npublic calculate(parameters: BinaryOperationParameters): Result {\nconst { operator, firstOperand, secondOperand } = parameters;\n\nswitch (operator) {\ncase \"*\":\nreturn firstOperand * secondOperand;\ncase \"+\":\nreturn firstOperand + secondOperand;\ncase \"-\":\nreturn firstOperand - secondOperand;\ncase \"/\":\nreturn firstOperand / secondOperand;\ncase \"**\":\nreturn firstOperand ** secondOperand;\ncase \"%\":\nreturn firstOperand % secondOperand;\ndefault:\nthrow new Error(\"Operator not found!\");\n}\n}\n}\nO problema aparece quando a calculadora precisa cobrir mais operações binárias entre inteiros: percentual, exponenciação, módulo, shift de bits. Cada funcionalidade nova altera a implementação original, sobe o acoplamento e encarece a manutenção.\n\n## O que é o padrão Strategy?\n\nO padrão define a funcionalidade por meio de um contrato (interface), implementado conforme o contexto. A interface define a operação; as implementações concretas definem sua execução.\n\nO código consumidor depende da abstração. Cada estratégia fica isolada em sua própria classe. Novos comportamentos entram sem alterar o código existente, alinhado ao Open/Closed Principle.\n\n## Definindo o contrato",
      "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "contrato",
        "cada",
        "parameters",
        "operator",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/artigos/design-patterns-strategy"
      }
    },
    {
      "id": "b1eaa340808bca1e",
      "url": "https://imrafaeldev.site/projects/post-engine-pt-br",
      "title": "Post Engine",
      "content": "## Problema\n\nGerar post “profissional” com LLM a partir de um slogan inventa biografia. O fluxo precisa entrevistar, identificar lacunas, preparar o briefing, redigir e exportar, e recusar o que não foi confirmado.\n\n## Restrições\n\nNúcleo em Python com fronteiras entre entrevista, geração, preservação de autoria, segmentação e persistência. Chamadas a modelo em workspace isolado, allowlist de provedor e prompts como contratos versionados. Avaliação híbrida: LLM mais heurísticas determinísticas. Ausência de experiência nunca vira falsa vivência.\n\n## Decisão\n\nEntrevistas adaptativas, briefing autoral, storyboard, veto a conteúdo fabricado, registry SQLite de prompts com rollback, interface textual e frontend React/Vite para revisar fases. Exportação Markdown ou SlideMark JSON após avaliação.\n\n## Estado atual\n\nBase com testes de entrevista, isolamento de LLM, registry, persistência e conversão SlideMark.\n\n## Limitações\n\nNão é um gerador genérico de thought leadership. Sem repertório real, o sistema recusa; não completa a biografia.",
      "description": "Workstation editorial centrada em autoria: entrevista, briefing, storyboard e exportação após o gateway barrar conteúdo fabricado. Prompts versionados; workspace LLM isolado.",
      "keywords": [
        "não",
        "biografia",
        "briefing",
        "entrevista",
        "persistência",
        "prompts",
        "avaliação",
        "registry",
        "slidemark",
        "problema"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "projeto-post-engine",
        "slug": "post-engine",
        "title": "Post Engine",
        "description": "Workstation editorial centrada em autoria: entrevista, briefing, storyboard e exportação após o gateway barrar conteúdo fabricado. Prompts versionados; workspace LLM isolado.",
        "excerpt": "A entrevista adaptativa extrai evidências; o gateway híbrido (LLM + heurística) barra vivência inventada. Apenas conteúdo confirmado segue para rascunho e exportação em Markdown ou SlideMark.",
        "repoUrl": "https://github.com/imrafaeldev/post-engine",
        "status": "Estação de trabalho editorial",
        "featured": "false",
        "order": "5",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/post-engine-pt-br.md"
      }
    },
    {
      "id": "b3435e109ba14048",
      "url": "https://imrafaeldev.site/es/proyectos/post-engine",
      "title": "Post Engine",
      "content": "- [Inicio](/es/)\n- [Proyectos](/es/proyectos/)\n- Post Engine\n\nEstación de trabajo editorial\n\n## Post Engine\n\nLa entrevista adaptativa extrae evidencias; el gateway híbrido (LLM + heurística) bloquea vivencia inventada. Solo el contenido confirmado sigue a borrador y exportación en Markdown o SlideMark.\n\n[Repositorio](https://github.com/imrafaeldev/post-engine)\n\nAutoria antes da geração\nEntrevista extrai evidência; briefing e storyboard preparam o material; o gateway veta fabricado. Só o confirmado segue para rascunho e export.\n\n- [01Problema](#problema)\n- [02Restricciones](#restricciones)\n- [03Decisión](#decision)\n- [04Estado actual](#estado-actual)\n- [05Limitaciones](#limitaciones)\n\n## Problema\n\nGenerar un post “profesional” con LLM a partir de un eslogan inventa biografía. El flujo necesita entrevistar, identificar lagunas, preparar el briefing, redactar y exportar, y rechazar lo no confirmado.\n\n## Restricciones\n\nNúcleo en Python con fronteras entre entrevista, generación, preservación de autoría, segmentación y persistencia. Llamadas a modelo en workspace aislado, allowlist de proveedor y prompts como contratos versionados. Evaluación híbrida: LLM más heurísticas deterministas. La ausencia de experiencia nunca se vuelve falsa vivencia.\n\n## Decisión\n\nEntrevistas adaptativas, briefing autoral, storyboard, veto a contenido fabricado, registry SQLite de prompts con rollback, interfaz textual y frontend React/Vite para revisar fases. Exportación Markdown o SlideMark JSON tras la evaluación.\n\n## Estado actual\n\nBase con pruebas de entrevista, aislamiento de LLM, registry, persistencia y conversión SlideMark.\n\n## Limitaciones\n\nNo es un generador genérico de thought leadership. Sin repertorio real, el sistema se niega; no completa la biografía.",
      "description": "Estación editorial centrada en autoría: entrevista, briefing, storyboard y exportación después de que el gateway bloquee contenido fabricado. Prompts versionados; workspace LLM aislado.",
      "keywords": [
        "entrevista",
        "post",
        "confirmado",
        "slidemark",
        "briefing",
        "proyectos",
        "engine",
        "gateway",
        "vivencia",
        "contenido"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/proyectos/post-engine"
      }
    },
    {
      "id": "b3ddbe70f39484f7",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-container-with-most-water",
      "title": "Desenoxidando la lógica #02: Container With Most Water (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- Desenoxidando la lógica #02: Container With Most Water\n\n## Desenoxidando la lógica #02: Container With Most Water\n\nIntenté elegir el próximo paso mirando solo a los vecinos. Funcionó en algunos casos, pero el problema pedía una visión más amplia.\n\n15 de septiembre de 2026\n\n- [Rendimiento](/es/articulos/?topic=performance)\n- [Trade-offs](/es/articulos/?topic=trade-offs)\n\nDespués de resolver el primer ejercicio de la serie, seguí en LeetCode para trabajar la lógica sin pedir una solución lista. El desafío 011 es [Container With Most Water](https://leetcode.com/problems/container-with-most-water/).\n\nRecibimos un array de alturas y necesitamos elegir dos líneas que formen el recipiente con el área mayor.\n\n## Cómo calcular el área\n\nSi elijo las posiciones left y right, el ancho es la distancia entre ellas. La altura del recipiente está limitada por la menor de las dos líneas.\n\nárea = min(altura izquierda, altura derecha) × distancia\nPor ejemplo, con estas alturas:\n\n[1, 8, 6, 2, 5, 4, 8, 3, 7]\nLas líneas en las posiciones 1 y 8 tienen alturas 8 y 7. La menor altura es 7, y la distancia entre ellas es 7. Esa combinación produce un área de 49.\n\n## Mi primer intento\n\nEmpecé con dos punteros, uno en cada punta del array. Después de calcular el área actual, simulaba dos posibilidades:\n\n- avanzar el puntero de la izquierda;\n\n- retroceder el puntero de la derecha.\n\nCalculaba el área de los dos próximos pares y elegía la mayor. El tramo principal era este:\n\nconst paddingLeftArea =\nMath.min(heights[leftIndex + 1], heights[rigthIndex]) *\n(rigthIndex - leftIndex + 1);\n\nconst paddingRightArea =\nMath.min(heights[leftIndex], heights[rigthIndex - 1]) *\n(rigthIndex - 1 - leftIndex);",
      "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
      "keywords": [
        "área",
        "altura",
        "heights",
        "leftindex",
        "rigthindex",
        "menor",
        "punteros",
        "maxarea",
        "problema",
        "leetcode"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/desenoxidando-logica-container-with-most-water"
      }
    },
    {
      "id": "b3fa9e988ce73164",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-pt-br",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 3)",
      "content": "Depois disso, o problema principal virou a divisão dos chunks. A estratégia inicial consumia memória demais. Em testes locais, com datasets menores, o crescimento proporcional chegou perto de 15% em alguns momentos.\n\nO incômodo vinha de uma expectativa errada: eu achei que mudar para Go automaticamente resolveria o problema. Na prática, eu só tinha movido o gargalo de lugar. Antes o limite estava mais claro na CPU. Depois, a estratégia de particionamento começou a pressionar memória.\n\nIsso pode acontecer por vários motivos: cópias desnecessárias, buffers grandes, slices mantendo referência para arrays maiores, filas internas grandes demais ou excesso de trabalho sendo preparado antes de ser processado.\n\nNo resultado final, a memória ainda cresceu cerca de 5%. Nesse caso, o ganho de tempo e CPU compensou a perda. Mas isso não é uma regra universal. Se a carga de produção fosse muito maior, ou se o serviço estivesse rodando com margem pequena de memória, essa troca poderia deixar de ser aceitável.\n\nParalelismo custa coordenação, alocação, sincronização e observabilidade. Não existe execução paralela grátis.\n\n## Quando eu manteria Node.js\n\nEu manteria Node.js sem incômodo para orquestração de I/O: comunicação por WebSocket, chamadas para várias APIs, consultas em banco, disparo de eventos, integração entre serviços e fluxos onde o tempo morto está na espera.\n\nNesses casos, o Event Loop é uma excelente escolha. Ele permite alto volume de operações concorrentes sem criar uma thread por requisição. Para aplicações orientadas a evento, isso é simples, produtivo e fácil de encaixar no ecossistema JavaScript.\n\nO erro é tentar empurrar esse mesmo modelo para um cálculo pesado e achar que concorrência de I/O vira paralelismo de CPU. Não vira.\n\n## Quando eu olharia para Go",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "mais",
        "mesmo",
        "tempo",
        "trabalho",
        "loop",
        "problema",
        "pode"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência",
        "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
        "excerpt": "Node.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-pt-br.md"
      }
    },
    {
      "id": "b48b55df801b3207",
      "url": "https://imrafaeldev.site/es/casos",
      "title": "Estudios de caso",
      "content": "- [Inicio](/es/)\n- Casos\n\n## Estudios de caso\n\nCada case documenta la restricción, la decisión, la alternativa descartada y el resultado medido. También indica cuándo conviene revisar la decisión.\n\n- Infosistemas feb/2025 – may/2026\n\n## [Infosistemas: contrato de fallo en la mensajería RabbitMQ](/es/casos/mensajeria-rabbitmq/)\n\nFallos intermitentes entre microservicios sin contrato para retry, DLQ o duplicidad. Más consumidores solo desplazaban la sobrecarga.\n\n**~98%** reducción de fallos intermitentes en los flujos críticos\n\n[Abrir el case — Infosistemas: contrato de fallo en la mensajería RabbitMQ](/es/casos/mensajeria-rabbitmq/)\n\n- VBET oct/2023 – feb/2025\n\n## [VBET: analítica sobre un SQL Server que no podíamos cambiar](/es/casos/analitica-sql-server-externo/)\n\nLa base era de otro equipo. El dashboard necesitaba dejar de depender de un schema que no controlábamos.\n\n**~7 min → <1 s** comisiones, de la carga original a la caché caliente\n\n[Abrir el case — VBET: analítica sobre un SQL Server que no podíamos cambiar](/es/casos/analitica-sql-server-externo/)\n\n- Flapper sep/2021 – jun/2022\n\n## [Flapper: descubrir el dominio antes de separar el monolito](/es/casos/modernizacion-monolito-sin-documentacion/)\n\nEl producto no podía parar, y los autores originales ya no estaban. Antes de migrar, fue preciso descubrir qué fronteras aún revelaba la base.\n\n**~25%** menos tablas en la separación por dominios\n\n[Abrir el case — Flapper: descubrir el dominio antes de separar el monolito](/es/casos/modernizacion-monolito-sin-documentacion/)",
      "description": "Cada case documenta la restricción, la decisión, la alternativa descartada y el resultado medido. También indica cuándo conviene revisar la decisión.",
      "keywords": [
        "casos",
        "case",
        "infosistemas",
        "contrato",
        "abrir",
        "vbet",
        "flapper",
        "descubrir",
        "antes",
        "2025"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/casos"
      }
    },
    {
      "id": "b48e3cb9c35b7a2b",
      "url": "https://imrafaeldev.site/experiences/south-system-quiq-itau-pt-br",
      "title": "South System, QUIQ e Itaú | Rafael Pereira",
      "content": "## O contexto\n\nNa South System, fui alocado na QUIQ para trabalhar em um Marketplace as a Service voltado a instituições financeiras. O primeiro contexto era o Itaú, mas o produto precisava receber novos bancos sem exigir um fork por cliente.\n\n## Como atuei\n\nParticipei do desenho da arquitetura, da modelagem de banco, da escolha de tecnologias e do refinamento das regras com produto. A solução foi construída como uma plataforma white-label e multi-tenant, com isolamento lógico entre tenants e Arquitetura Hexagonal para manter o domínio separado das integrações específicas.\n\nTrabalhei com Node.js, TypeScript, MySQL, serviços assíncronos em Go e AWS. Estruturei testes unitários e de integração para os casos críticos e usei análise estática como parte do fluxo de qualidade.\n\n## O que levo\n\nEssa experiência mudou minha forma de comunicar arquitetura. Passei a tratar alinhamento com produto, PO e stakeholders como parte da decisão técnica, não como uma etapa posterior ao código.",
      "description": "Minha experiência com marketplace white-label e multi-tenant para instituições financeiras.",
      "keywords": [
        "como",
        "para",
        "produto",
        "arquitetura",
        "contexto",
        "parte",
        "south",
        "system",
        "alocado",
        "quiq"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-south-system-quiq-itau",
        "slug": "south-system-quiq-itau",
        "title": "South System, QUIQ e Itaú | Rafael Pereira",
        "description": "Minha experiência com marketplace white-label e multi-tenant para instituições financeiras.",
        "company": "South System (alocado na QUIQ/Itaú)",
        "role": "Engenheiro Backend",
        "period": "jun/2022 a abr/2023",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/south-system-quiq-itau-pt-br.md"
      }
    },
    {
      "id": "b50a59d747cf7a2d",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-en",
      "title": "Design Patterns: Strategy (Part 3)",
      "content": "The context receives and executes the strategy. It knows only the contract, not the concrete implementation. The old `DefaultCalculator` starts receiving this `ContextAnalyzer` by injection:\n\n```typescript\nclass Calculator {\n  constructor(private readonly contextAnalyzer: ContextAnalyzer) {}\n\n  /**\n   * The calculate implementation in Calculator does not change per operation.\n   * What grows is the ContextAnalyzer, which adds one case per new operation.\n   */\n  public calculate(parameters: BinaryOperationParameters): Result {\n    const { operator, firstOperand, secondOperand } = parameters;\n    return this.contextAnalyzer\n      .getInstance(operator)\n      .calculate({ firstOperand, secondOperand });\n  }\n}\n```\n\n## Why use Strategy?\n\nStrategy helps in legacy code with several business rules, each represented by an `if` and a long implementation. The shared part stays in the contract; each rule variation stays in its own class; a context analyzer (resolver/factory) picks the strategy.\n\nOn each request, the code evaluates the operation context and selects the implementation matching the contract.\n\n## Relation to other patterns\n\n- Adapter: Strategy varies behavior; Adapter isolates external dependencies behind an owned interface.\n- SOLID (OCP): Strategy is one way to apply the Open/Closed Principle.",
      "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "class",
        "operator",
        "parameters",
        "each",
        "operation"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
        "excerpt": "If/else chains grow and turn fragile. Strategy isolates each algorithm behind a contract.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-en.md"
      }
    },
    {
      "id": "b76c5b5ad34fd2d0",
      "url": "https://imrafaeldev.site/experiences/eds-policia-civil-rio-en",
      "title": "EDS and Rio de Janeiro Civil Police | Rafael Pereira",
      "content": "## Context\n\nIn my consulting work for EDS, I worked on systems for the Civil Police of Rio de Janeiro. The context involved a critical public operation, a high-volume health management system, and an evolving legal ERP.\n\n## How I worked\n\nI structured the health system backend with NestJS and SQL Server. I also refactored legacy routes and contributed to flows for process automation, document management, and evidence collection. Security, access control, traceability, and LGPD compliance guided how every route had to evolve.\n\nBeyond backend work, I collaborated on shared design-system components to align API contracts with the interfaces used in the operation.\n\n## What I took from it\n\nThe work reinforced the care needed to evolve sensitive systems without losing auditability. Rather than separating security from delivery, I treated access and traceability as part of the product contract.",
      "description": "My consulting experience on sensitive public systems.",
      "keywords": [
        "work",
        "context",
        "worked",
        "systems",
        "operation",
        "health",
        "management",
        "system",
        "backend",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-eds-policia-civil-rio",
        "slug": "eds-policia-civil-rio",
        "title": "EDS and Rio de Janeiro Civil Police | Rafael Pereira",
        "description": "My consulting experience on sensitive public systems.",
        "company": "EDS (Civil Police of Rio de Janeiro)",
        "role": "Backend Engineer — Consulting",
        "period": "Jul 2025 to Dec 2025",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/eds-policia-civil-rio-en.md"
      }
    },
    {
      "id": "b8470926debc48e7",
      "url": "https://imrafaeldev.site/en/articles/derusting-logic-group-anagrams",
      "title": "Derusting logic #01: Group Anagrams (Part 3)",
      "content": "groups[key] = append(groups[key], str)\n}\n\nresult := make([][]string, 0, len(groups))\n\nfor _, group := range groups {\nresult = append(result, group)\n}\n\nreturn result\n}\n\n## What changed in complexity?\n\n- Sorting: O(k log k) per string and O(n × k log k) for n strings.\n\n- Counting: O(k) per string and O(n × k) for n strings.\n\nThe improvement appeared when I realized sorting did work the problem never asked for. The main change is from O(k log k) to O(k) per string.\n\n## The first solution was not wrong\n\nIt solves the problem and, depending on context, could be enough. The habit I wanted to recover was to keep thinking after the code starts working. Find a solution, return to the problem, and ask: what work is my algorithm doing without needing to?\n\nI am not doing these exercises because LeetCode represents all software engineering work, nor to chase the most sophisticated solution. I am doing them because I noticed that using AI every day reduced how often I insist on a problem alone. I want to reserve space to exercise that again: read, try, fail, review the approach, and only then compare paths.\n\n---\n\n*Series Derusting logic #01 — Group Anagrams. Adapted from the original carousel; problem at [leetcode.com/problems/group-anagrams](https://leetcode.com/problems/group-anagrams/).*\n\nChave por sort e mapa\nCada string vira chave ordenada (eat → aet). O mapa agrupa anagramas sob a mesma chave sem comparar cada par.\n\nContagem [26]uint8 vs sort\nVetor de frequências a–z vira chave. Contagem custa O(k) por string; sort custava O(k log k). Total: de O(n × k log k) para O(n × k).",
      "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
      "keywords": [
        "string",
        "group",
        "that",
        "result",
        "anagrams",
        "same",
        "solution",
        "what",
        "each",
        "problem"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/en/articles/derusting-logic-group-anagrams"
      }
    },
    {
      "id": "b8a0d8d85bd0b1be",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-pt-br",
      "title": "Desenferrujando a lógica #01: Group Anagrams (Part 1)",
      "content": "Depois de anos programando, percebi que minha lógica estava enferrujada. Eu não tinha parado de escrever código. O que mudou foi que, aos poucos, comecei a terceirizar partes do raciocínio que antes precisava exercitar sozinho.\n\nEscolhi o exercício [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/) no LeetCode. A proposta é receber uma lista de strings e reunir os anagramas no mesmo grupo.\n\n## O que precisamos resolver\n\nEntrada:\n\n```text\n[\"eat\", \"tea\", \"tan\", \"ate\", \"nat\", \"bat\"]\n```\n\nResultado possível:\n\n```text\n[\"eat\", \"tea\", \"ate\"]\n[\"tan\", \"nat\"]\n[\"bat\"]\n```\n\nA ordem dos grupos não importa.\n\n## O que é um anagrama?\n\nPegue `eat`, `tea` e `ate`. Cada uma tem `a` uma vez, `e` uma vez e `t` uma vez. A posição muda; a quantidade de cada letra continua igual.\n\nO algoritmo precisa transformar essas palavras em uma representação comum. Se as três produzirem a mesma chave, posso usar essa chave em um `map` e colocá-las no mesmo grupo. O primeiro problema é criar essa chave.\n\n## Minha primeira resposta foi ordenar\n\nComecei usando a string ordenada como chave:\n\n```text\neat → aet\ntea → aet\nate → aet\n```\n\nAs três produzem `aet`.\n\n### Solução usando sort\n\nEssa foi a primeira solução que me ocorreu. Eu não estava tentando a implementação mais enxuta de imediato. Queria montar uma solução coerente e entender onde ela poderia melhorar.\n\n```go\nfunc sortString(str string) string {\n\tb := []byte(str)\n\tslices.Sort(b)\n\treturn string(b)\n}\n\nfunc groupAnagrams(strs []string) [][]string {\n\tmapping := make(map[string][]string)\n\n\tfor _, str := range strs {\n\t\tsortedStr := sortString(str)\n\t\tmapping[sortedStr] = append(mapping[sortedStr], str)\n\t}\n\n\tresult := make([][]string, 0, len(mapping))\n\n\tfor _, group := range mapping {\n\t\tresult = append(result, group)\n\t}\n\n\treturn result\n}\n```\n\nO que acontece nesse código:",
      "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "chave",
        "não",
        "solução",
        "result",
        "problema",
        "groups",
        "group"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "desenferrujando-logica-group-anagrams",
        "title": "Desenferrujando a lógica #01: Group Anagrams",
        "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
        "excerpt": "Eu não tinha parado de escrever código. O que mudou foi terceirizar partes do raciocínio. Group Anagrams foi o exercício para recuperar o hábito.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-pt-br.md"
      }
    },
    {
      "id": "b91228f4fc9e05ec",
      "url": "https://imrafaeldev.site/cases/infosistemas-mensageria-es",
      "title": "Infosistemas: contrato de fallo en la mensajería RabbitMQ (Part 1)",
      "content": "## Contexto\n\nInfosistemas opera plataformas de gestión para arrendadoras, flotas y automotrices. El trabajo ocurrió en el equipo de arquitectura, en colaboración con DevOps, SREs y DBAs, entre febrero de 2025 y mayo de 2026.\n\nEste caso cubre el frente de mensajería. Otros frentes de la misma experiencia (seguridad del ERP, jornadas de firma, integraciones fiscales) existen en las fuentes, pero no entran aquí como número o afirmación extra.\n\n## Restricciones\n\nLos flujos críticos cruzaban microservicios. El fallo era intermitente: la misma operación podía completarse en una ejecución y no completarse en la siguiente. Aumentar concurrencia o prefetch sin criterio transfería sobrecarga a consumidores, servicios o bases downstream.\n\n## Problema\n\nLos mensajes dejaban de completar el flujo esperado. Investigar un fallo parcial era difícil. No había contrato explícito para fallo temporal, fallo permanente, duplicidad o poison message.\n\n## Decisión\n\nLa mensajería fue rediseñada para volver predecible el comportamiento ante fallos:\n\n- colas durables;\n- DLQ por flujo, para el mensaje que no debe desaparecer ni repetirse sin control;\n- retry con backoff para indisponibilidad temporal;\n- idempotencia y deduplicación en el consumidor, porque la entrega duplicada no puede repetir el efecto de negocio;\n- publisher confirms, para reducir la incertidumbre en la publicación;\n- ajuste de prefetch, en lugar de abrir concurrencia indiscriminada.\n\n## Alternativa descartada\n\nTratar el problema como falta de capacidad (más consumidores, más prefetch) sin cambiar el contrato de fallo. Eso movería el cuello de botella y mantendría pérdida o duplicidad silenciosa.\n\n## Implementación",
      "description": "Rediseño de la mensajería RabbitMQ en Infosistemas con colas durables, DLQ, retry, idempotencia y prefetch. El trabajo redujo en cerca del 98% los fallos intermitentes entre microservicios.",
      "keywords": [
        "para",
        "fallo",
        "prefetch",
        "flujos",
        "otros",
        "frentes",
        "críticos",
        "microservicios",
        "operación",
        "consumidores"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "infosistemas-mensageria",
        "slug": "mensajeria-rabbitmq",
        "title": "Infosistemas: contrato de fallo en la mensajería RabbitMQ",
        "description": "Rediseño de la mensajería RabbitMQ en Infosistemas con colas durables, DLQ, retry, idempotencia y prefetch. El trabajo redujo en cerca del 98% los fallos intermitentes entre microservicios.",
        "company": "Infosistemas",
        "role": "Ingeniero de Software Sénior / Arquitecto de Software",
        "period": "feb/2025 – may/2026",
        "excerpt": "Fallos intermitentes entre microservicios sin contrato para retry, DLQ o duplicidad. Más consumidores solo desplazaban la sobrecarga.",
        "proofValue": "~98%",
        "proofLabel": "reducción de fallos intermitentes en los flujos críticos",
        "featuredClaimId": "infosistemas-rabbitmq-reliability",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/infosistemas-mensageria-es.md"
      }
    },
    {
      "id": "b93540001a1415da",
      "url": "https://imrafaeldev.site/artigos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 5)",
      "content": "Em alguns times, usar worker threads pode ser suficiente e mais barato do que introduzir Go. Em outros, separar o processamento CPU bound em um serviço Go pode ser mais simples de operar e escalar.\n\nA decisão não deveria nascer de preferência por linguagem. Deveria nascer da natureza da carga.\n\n## A regra prática que ficou\n\nDepois desse caso, a regra que ficou para mim é esta: se o problema é esperar muita coisa ao mesmo tempo, Node.js com Event Loop tende a orquestrar muito",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "loop",
        "mesmo",
        "trabalho",
        "event",
        "problema",
        "mais",
        "tempo"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/artigos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "b9697f71d07f9020",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-pt-br",
      "title": "Design Patterns: Strategy (Part 1)",
      "content": "Cadeias de `if/else` crescem e ficam frágeis. O padrão Strategy encapsula cada algoritmo em sua própria classe e permite trocar implementações em tempo de execução sem alterar o código que as consome.\n\n## O problema: if/else infinito\n\nUma calculadora com soma, subtração, multiplicação e divisão costuma nascer como uma classe `DefaultCalculator`: métodos privados por operação e uma função pública que escolhe qual invocar com `switch` ou cadeia de `if/else`.\n\n```typescript\nclass DefaultCalculator {\n  public calculate(parameters: BinaryOperationParameters): Result {\n    const { operator, firstOperand, secondOperand } = parameters;\n\n    switch (operator) {\n      case \"*\":\n        return firstOperand * secondOperand;\n      case \"+\":\n        return firstOperand + secondOperand;\n      case \"-\":\n        return firstOperand - secondOperand;\n      case \"/\":\n        return firstOperand / secondOperand;\n      case \"**\":\n        return firstOperand ** secondOperand;\n      case \"%\":\n        return firstOperand % secondOperand;\n      default:\n        throw new Error(\"Operator not found!\");\n    }\n  }\n}\n```\n\nO problema aparece quando a calculadora precisa cobrir mais operações binárias entre inteiros: percentual, exponenciação, módulo, shift de bits. Cada funcionalidade nova altera a implementação original, sobe o acoplamento e encarece a manutenção.\n\n## O que é o padrão Strategy?\n\nO padrão define a funcionalidade por meio de um contrato (interface), implementado conforme o contexto. A interface define a operação; as implementações concretas definem sua execução.\n\nO código consumidor depende da abstração. Cada estratégia fica isolada em sua própria classe. Novos comportamentos entram sem alterar o código existente, alinhado ao Open/Closed Principle.\n\n## Definindo o contrato\n\nO primeiro passo é a interface do contrato da estratégia. Na calculadora, algo que receba dois números e retorne o resultado:",
      "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "parameters",
        "operator",
        "cada",
        "strategy",
        "operação",
        "calculate"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
        "excerpt": "Cadeias de if/else crescem e ficam frágeis. O Strategy isola cada algoritmo atrás de um contrato.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-pt-br.md"
      }
    },
    {
      "id": "babb446ab3997e72",
      "url": "https://imrafaeldev.site/es/experiencias/braistech",
      "title": "Braistech",
      "content": "- [Inicio](/es/)\n- Braistech\n\n## Braistech\n\nMi experiencia en Braistech con producto, microservicios y criptoactivos.\n\nCargo\n\nIngeniero de Software Full Stack Pleno\n\nPeríodo\n\nnov/2019 a ene/2021\n\n## Contexto\n\nEn Braistech, tuve una de mis primeras experiencias de producto en un entorno pequeño, con pocas personas y responsabilidad distribuida. El dominio involucraba contratos de criptoactivos y movimientos financieros.\n\n## Cómo trabajé\n\nLideré la estructuración del sistema principal con Node.js y NestJS, participé en el diseño de microservicios para el núcleo del negocio y desarrollé aplicaciones Flutter. También construí un sistema de contratos y trabajé en integraciones de pago relacionadas con el ecosistema de Binance.\n\nParticipé además en la transición de una organización MVC hacia Clean Architecture. El objetivo era reducir acoplamiento y facilitar el mantenimiento de un sistema en crecimiento, mientras orientaba a desarrolladores junior en las decisiones de código.\n\n## Lo que me llevé\n\nEsta etapa consolidó mi interés por backend y arquitectura. Trabajar en todo el producto también me dio una visión full stack que sigue siendo útil en conversaciones con frontend y producto.",
      "description": "Mi experiencia en Braistech con producto, microservicios y criptoactivos.",
      "keywords": [
        "braistech",
        "producto",
        "sistema",
        "microservicios",
        "criptoactivos",
        "full",
        "stack",
        "contratos",
        "trabajé",
        "participé"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/braistech"
      }
    },
    {
      "id": "baf7b1653e6d9fde",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-container-with-most-water-pt-br",
      "title": "Desenferrujando a lógica #02: Container With Most Water (Part 1)",
      "content": "Depois de resolver o primeiro exercício da série, continuei no LeetCode para trabalhar a lógica sem pedir uma solução pronta. O desafio 011 é o [Container With Most Water](https://leetcode.com/problems/container-with-most-water/).\n\nRecebemos um array de alturas e precisamos escolher duas linhas que formem o recipiente com a maior área.\n\n## Como calcular a área\n\nSe escolho as posições `left` e `right`, a largura é a distância entre elas. A altura do recipiente é limitada pela menor das duas linhas.\n\n```text\nárea = min(altura da esquerda, altura da direita) × distância\n```\n\nPor exemplo, com estas alturas:\n\n```text\n[1, 8, 6, 2, 5, 4, 8, 3, 7]\n```\n\nAs linhas nas posições `1` e `8` têm alturas `8` e `7`. A menor altura é `7`, e a distância entre elas é `7`. Essa combinação produz uma área de `49`.\n\n## Minha primeira tentativa\n\nComecei com dois ponteiros, um em cada ponta do array. Depois de calcular a área atual, eu simulava duas possibilidades:\n\n- avançar o ponteiro da esquerda;\n- recuar o ponteiro da direita.\n\nEu calculava a área dos dois próximos pares e escolhia o maior. O trecho principal era este:\n\n```javascript\nconst paddingLeftArea =\n  Math.min(heights[leftIndex + 1], heights[rigthIndex]) *\n  (rigthIndex - leftIndex + 1);\n\nconst paddingRightArea =\n  Math.min(heights[leftIndex], heights[rigthIndex - 1]) *\n  (rigthIndex - 1 - leftIndex);\n\nif (paddingLeftArea > paddingRightArea && paddingLeftArea > maxArea) {\n  leftIndex += 1;\n} else {\n  rigthIndex -= 1;\n}\n```\n\nO problema estava na hipótese. A melhor decisão local não garante a melhor área no restante do array. Eu tentava adivinhar o caminho olhando apenas para os dois próximos movimentos. Também havia um erro na fórmula da distância desse rascunho: para um par de posições, a largura é `right - left`.\n\n## A observação que destrava o problema\n\nA área depende de duas coisas: largura e menor altura.",
      "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
      "keywords": [
        "área",
        "heights",
        "leftindex",
        "rigthindex",
        "altura",
        "ponteiros",
        "maxarea",
        "para",
        "dois",
        "javascript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "desenferrujando-logica-container-with-most-water",
        "title": "Desenferrujando a lógica #02: Container With Most Water",
        "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
        "excerpt": "Eu tentei escolher o próximo passo olhando apenas para os vizinhos. Funcionou em alguns casos, mas o problema pedia uma visão mais ampla.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-container-with-most-water-pt-br.md"
      }
    },
    {
      "id": "bc14eea869a66134",
      "url": "https://imrafaeldev.site/es",
      "title": "Rafael Pereira, ingeniero de software sénior (Part 3)",
      "content": "[Abrir el proyecto](/es/proyectos/goc-mcp/)\n\nOrquestração local via MCP\nMaestro delega; daemon FIFO coordena um worker OpenCode por vez; SQLite guarda estado. A hipótese de eficiência não se confirmou.\n\nProducto propio, desktop\n\n### [md2cv](/es/proyectos/md2cv/)\n\nPerfil y versiones inmutables quedan en el SQLite de la máquina. El agente supervisado solo propone cambios bajo schema; ATS y exportación en PDF/DOCX reutilizan el mismo grafo, sin backend SaaS dueño de los datos.\n\n[Abrir el proyecto](/es/proyectos/md2cv/)\n\nGrafo local e agente supervisionado\nPerfil e versões no SQLite; candidatura no mesmo contexto; agente só propõe sob schema; ATS e export reutilizam o grafo sem SaaS dono.\n\n[Ver todos los proyectos](/es/proyectos/)\n\nRecorrido\n\n## Una trayectoria de sistemas, no de cargos\n\n-\n2025 a 2026\n\n### Arquitectura\n\nPlataformas de alquiler y flotas. La prueba pública de esta etapa es la mensajería.\n\nInfosistemas\n\n-\n2023 a 2025\n\n### Analytics sobre una base externa\n\nSQL Server de otro equipo. El dashboard tenía que funcionar sin depender de un schema que no controlábamos.\n\nVBET\n\n-\n2023\n\n### Postventa sobre legado\n\nEl core en PHP 5.7 sostenía la operación. La automatización liberó soporte sin reescribir el monolito.\n\nMaxmilhas\n\n-\n2022 a 2023\n\n### Marketplace multi-tenant\n\nWhite-label para incorporar bancos sin fork por cliente.\n\nSouth System / QUIQ-Itaú\n\n-\n2021 a 2022\n\n### Modernización incremental\n\nEl monolito no tenía a sus autores originales. La migración empezó con lo que la base de datos todavía dejaba leer.\n\nFlapper\n\nPráctica\n\n## Cómo avanza el trabajo\n\nCada etapa limita la siguiente. Sin esas restricciones, la arquitectura se vuelve gusto personal.\n\nCómo avanza el trabajo\n\n- Contexto\n- Restricción\n- Decisión\n- Evidencia\n- Límite\n\n01\n\n### Contexto\n\nQuién sufre cuando esto se rompe, y en qué operación.\n\n02\n\n### Restricción\n\nLo que no puede parar, cambiar o tratarse como capacidad extra.\n\n03\n\n### Decisión",
      "description": "Portafolio institucional y hub editorial de Rafael Pereira. Trabajo en los puntos en los que los sistemas simples dejan de ser simples.",
      "keywords": [
        "casos",
        "proyectos",
        "abrir",
        "case",
        "qué",
        "caso",
        "fallos",
        "decisión",
        "contacto",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/es"
      }
    },
    {
      "id": "bcc1b1f683f1af69",
      "url": "https://imrafaeldev.site/articles/derusting-logic-container-with-most-water-en",
      "title": "Derusting logic #02: Container With Most Water (Part 2)",
      "content": "When the pointers are at positions `left` and `right`, moving the pointer of the taller line cannot raise the container's minimum height. The width always shrinks, and the height limiting the area is still there.\n\nSo the pointer that must advance is the one at the shorter height. It is the only move that can find a taller line and make up for the lost width.\n\nIf the heights are equal, either one can advance. In the code, I chose to advance the left one when `heights[leftIndex] <= heights[rigthIndex]`.\n\n## Two-pointer solution\n\n```javascript\n/**\n * @param {number[]} heights\n * @return {number}\n */\nvar maxArea = function (heights) {\n  let leftIndex = 0;\n  let rigthIndex = heights.length - 1;\n  let maxArea = 0;\n\n  while (leftIndex < rigthIndex) {\n    const minH = Math.min(heights[leftIndex], heights[rigthIndex]);\n    const currentArea = minH * (rigthIndex - leftIndex);\n\n    maxArea = Math.max(maxArea, currentArea);\n\n    if (heights[leftIndex] <= heights[rigthIndex]) {\n      leftIndex++;\n    } else {\n      rigthIndex--;\n    }\n  }\n\n  return maxArea;\n};\n```\n\nEach round, I compute the current pair's area, update the largest area found, and move one of the pointers. The `while` loop converges toward the center and ends.\n\nThe advancing detail matters. In the version I had written, the pointers only advanced when the current area was not larger than `maxArea`. If a new maximum area was found, the same combination would be computed again, never leaving the loop. The fix was to separate the two decisions: record the area, then move the shorter-height pointer.\n\n## The result of the attempts\n\nThe LeetCode history looked like this:\n\n- JavaScript: accepted, `3 ms` and `63.6 MB`.\n- JavaScript: wrong answer.\n- JavaScript: wrong answer.\n- Go: accepted, `0 ms` and `9.6 MB`.\n- TypeScript: accepted, `3 ms` and `63.9 MB`.\n- Go: wrong answer.",
      "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
      "keywords": [
        "heights",
        "area",
        "leftindex",
        "rigthindex",
        "that",
        "height",
        "maxarea",
        "left",
        "pointers",
        "javascript"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "derusting-logic-container-with-most-water",
        "title": "Derusting logic #02: Container With Most Water",
        "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
        "excerpt": "I tried to pick the next step by looking only at the neighbors. It worked in some cases, but the problem asked for a wider view.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/derusting-logic-container-with-most-water-en.md"
      }
    },
    {
      "id": "bd54c33ead3cb9be",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 11)",
      "content": "**Mecanismo.** Profiling coleta amostras do que o programa realmente faz: perfil de CPU mostra onde o tempo é gasto, perfil de heap mostra onde a memória é alocada, e os perfis de goroutine e bloqueio mostram onde a concorrência trava (espera em mutex, canal vazio, I/O). O fluxo padrão é reproduzir a carga, capturar com `net/http/pprof` ou `runtime/pprof`, comparar antes/depois e só então otimizar o caminho quente comprovado.\n\n**Falha ou limite que ele trata.** Sem perfil, otimizações miram o lugar errado: reduz-se alocação em código frio enquanto o gargalo real é contenção de lock ou milhares de goroutines paradas no mesmo canal. O limite: perfis são amostras estatísticas, não verdades absolutas — cargas sintéticas curtas e benchmarks sem representatividade produzem conclusões falsas, e ativar profiling contínuo com overhead alto em produção pode distorcer as medições.\n\n**Exemplo de aplicação.** Cenário didático: expor o endpoint de profiling em um servidor de exercício e capturar CPU por 30 segundos.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n)\n\nfunc busy(n int) int {\n\ttotal := 0\n\tfor i := 0; i < n; i++ {\n\t\ttotal += i * i\n\t}\n\treturn total\n}\n\nvar sink int\n\nfunc main() {\n\t// Em exercício local: http://localhost:6060/debug/pprof/\n\tgo func() {\n\t\tif err := http.ListenAndServe(\"localhost:6060\", nil); err != nil && err != http.ErrServerClosed {\n\t\t\tfmt.Println(\"pprof:\", err)\n\t\t}\n\t}()\n\n\t// Carga contínua durante a captura; mantém o servidor vivo.\n\tfor {\n\t\tsink = busy(1_000_000)\n\t}\n}\n```\n\n```sh\n# Captura 30s de CPU e abre o relatório interativo:\ngo tool pprof http://localhost:6060/debug/pprof/profile?seconds=30\n# Dentro do pprof: top, list busy, web\n```",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 10,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "bd78c9a93bf7c94e",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 3)",
      "content": "Nenhum desses sinais fecha o diagnóstico. Um N+1 numa rota com dois itens pode ter impacto irrelevante, um índice novo pode ajudar pouco numa coluna com baixa seletividade e um join volumoso talvez seja necessário para produzir o resultado. Por isso, conto as consultas, cronometro o loop inteiro e confiro no plano quantas linhas e leituras foram produzidas.\n\nEsse radar apenas escolhe a primeira medição. A próxima camada da investigação vem da conta.\n\n## Uma linha correta pode desperdiçar o índice\n\nUma dessas linhas costuma passar batida na revisão. Ela devolve os registros de um dia inteiro.\n\n```sql\nWHERE CAST(campo_data_hora AS date) = @data\n```\n\nO resultado da tela parece correto. No plano, a história pode ser outra. Aplicar `CAST` à coluna obriga a consulta a transformar os valores antes da comparação. Com um índice em `campo_data_hora`, isso pode impedir uma busca direta pelo intervalo, aumentar bastante as leituras e até levar a um scan.\n\nQuando o pedido é pelos registros de um dia inteiro, calculo as bordas fora da coluna.\n\n```sql\nWHERE campo_data_hora >= @inicio_do_dia\n  AND campo_data_hora < @inicio_do_proximo_dia\n```\n\nSe o início é 16 de julho à meia-noite, o limite seguinte é 17 de julho à meia-noite. Assim entram todos os valores do dia 16, inclusive aqueles com frações de segundo no final, sem depender de `23:59:59.999`.\n\nO resultado permanece correto, mas agora existe um intervalo que o índice pode percorrer. A confirmação vem da comparação das leituras e do operador de acesso nos dois planos. Se a métrica não mudar, a hipótese não ficou de pé.\n\n## Leia o plano pela sequência do trabalho\n\nDepois de comparar as versões do filtro, leio o plano como uma história do trabalho produzido pela consulta. Começo pelo tempo total e pelas leituras. Se a tela devolve dez indicadores, mas a execução faz centenas de milhares de leituras, existe uma conta para explicar.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "bd7adf1e3a9b0b75",
      "url": "https://imrafaeldev.site/projects/post-engine-en",
      "title": "Post Engine",
      "content": "## Problem\n\nGenerating a \"professional\" post with an LLM from a slogan invents biography. The flow must interview, identify gaps, prepare the briefing, draft, and export — and refuse what was not confirmed.\n\n## Constraints\n\nPython core with boundaries between interview, generation, authorship preservation, segmentation, and persistence. Model calls in an isolated workspace, provider allowlist, and prompts as versioned contracts. Hybrid evaluation: LLM plus deterministic heuristics. Missing experience never becomes false lived experience.\n\n## Decision\n\nAdaptive interviews, authorial briefing, storyboard, veto on fabricated content, SQLite prompt registry with rollback, textual interface, and React/Vite frontend to review phases. Markdown or SlideMark JSON export after evaluation.\n\n## Current state\n\nBase with tests for interview, LLM isolation, registry, persistence, and SlideMark conversion.\n\n## Limitations\n\nIt is not a generic thought-leadership generator. Without real repertoire, the system refuses; it does not complete the biography.",
      "description": "Authorship-centered editorial workstation: interview, briefing, storyboard, and export after the gateway blocks fabricated content. Versioned prompts; isolated LLM workspace.",
      "keywords": [
        "with",
        "interview",
        "biography",
        "briefing",
        "export",
        "persistence",
        "evaluation",
        "experience",
        "registry",
        "slidemark"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "projeto-post-engine",
        "slug": "post-engine",
        "title": "Post Engine",
        "description": "Authorship-centered editorial workstation: interview, briefing, storyboard, and export after the gateway blocks fabricated content. Versioned prompts; isolated LLM workspace.",
        "excerpt": "The adaptive interview extracts evidence; the hybrid gateway (LLM + heuristics) blocks invented lived experience. Only confirmed content moves to draft and Markdown or SlideMark export.",
        "repoUrl": "https://github.com/imrafaeldev/post-engine",
        "status": "Editorial workstation",
        "featured": "false",
        "order": "5",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/post-engine-en.md"
      }
    },
    {
      "id": "bdf2404c6170ead6",
      "url": "https://imrafaeldev.site/pages/probe-pt-br",
      "title": "Verificação da fundação",
      "content": "Esta rota existe só para verificar a fundação multilíngue. Não faz parte da navegação pública nem do sitemap.",
      "description": "Página temporária, sem índice, usada para validar a troca de idioma quando não há variante publicada.",
      "keywords": [
        "esta",
        "rota",
        "existe",
        "só",
        "para",
        "verificar",
        "fundação",
        "multilíngue",
        "não",
        "parte"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "foundation-probe",
        "slug": "fundacao",
        "title": "Verificação da fundação",
        "description": "Página temporária, sem índice, usada para validar a troca de idioma quando não há variante publicada.",
        "heading": "Página sem variantes em inglês ou espanhol",
        "lede": "O seletor de idioma deve levar à home do idioma escolhido, não a um fallback em português.",
        "indexable": "false",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "pages/probe-pt-br.md"
      }
    },
    {
      "id": "be0c32a029d977ea",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-es",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 1)",
      "content": "Una API se vuelve lenta y la reunión ya se llena de soluciones antes de ganar una medición. Aparecen más CPU y RAM, caché, cola, microservicios y refactorización. A veces alguien propone hasta cambiar el lenguaje. Raramente la primera sugerencia es abrir el plan de ejecución.\n\nPor eso, empiezo la investigación cerca de los datos. No porque la base sea la culpable por defecto, sino porque mucho pasa por ahí y esa verificación suele dar señal rápida. Si la consulta está saludable, saco la base del frente y sigo el flujo de la petición.\n\n## Soluciones rápidas también esconden trabajo caro\n\nCaché y cola resuelven problemas reales. Más máquina también puede ser la decisión correcta. Cuando entran por reflejo, sin embargo, esos recursos pueden solo cambiar el tamaño de la cuenta mientras nadie sabe qué tramo de la petición está reteniendo la respuesta.\n\nSubir CPU reduce la disputa por recurso, la caché saca algunas peticiones del camino y la cola absorbe un pico. Mientras tanto, una consulta que hace un trabajo enorme para devolver casi nada sigue cara en cada ejecución. Lo mismo vale para un loop que dispara una llamada por ítem.\n\nLa latencia puede caer por un tiempo, la alerta deja de sonar y el equipo respira. Cuando la carga crece, la operación cara reaparece, ahora acompañada de una infraestructura mayor.\n\nLa pregunta que debe venir antes de la discusión de arquitectura: ¿cuánto trabajo está produciendo esta petición para entregar el resultado?\n\n## Casi duplicamos la máquina y el dashboard siguió lento\n\nFue lo que pasó en un dashboard en el que trabajé. Cargaba de forma síncrona, y una única consulta hacía todo a la vez: buscaba los datos, cruzaba varias informaciones y calculaba los valores exhibidos. Como la CPU de la base quedaba muy alta, casi duplicamos CPU y RAM.\n\nLa base quedó más grande. El dashboard siguió pasando del minuto para cargar. La misma consulta aún concentraba todo el trabajo pesado en una única ejecución.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "consulta",
        "base",
        "plan",
        "para",
        "puede",
        "antes",
        "después",
        "lecturas",
        "cuando",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-cerca-de-los-datos",
        "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos",
        "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
        "excerpt": "API lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-es.md"
      }
    },
    {
      "id": "be976dbc7dc211d4",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-pt-br",
      "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter (Part 2)",
      "content": "O serviço perde o conhecimento de como o CRUD chega ao banco. Ele é composto pelos protocolos; a implementação concreta entra em tempo de execução — por injeção de dependência. Enquanto usamos PostgreSQL, implementamos os protocolos nos adapters (ou connectors).\n\n`CreateDatabaseCustomerProtocol` pode ser implementado por `CreateDatabaseCustomerPostgresqlAdapter`, `CreateDatabaseCustomerMysqlAdapter`, `CreateDatabaseCustomerMongoDBAdapter` ou `CreateDatabaseCustomerMockedAdapter`. O serviço fica assim:\n\n```typescript\nclass CustomerService {\n  constructor(\n    private readonly createCustomer: CreateDatabaseCustomerProtocol,\n  ) {}\n\n  public register(customer: CustomerInputEntity): SuccessfulEntityCreation {\n    return this.createCustomer.createCustomerOnDatabase(customer);\n  }\n}\n```\n\nPara o serviço, tanto faz se o banco devolve JSON, XML ou outro formato — o adapter traduz para o contrato que a regra de negócio espera.\n\n## Vantagens\n\n- **Manutenção:** qualquer plugin pode ser substituído sem reescrever o serviço.\n- **Testes:** para testar só a regra de negócio, injete um adapter mock que implementa o mesmo protocolo.\n- **Código limpo:** responsabilidades separadas; a regra de negócio não carrega detalhes do driver.\n\n## Próximo passo\n\nPegue um frontend com dezenas de bibliotecas e identifique o que você realmente usa. Escolha uma funcionalidade — converter real em dólar, por exemplo. Descreva o contrato (entrada e saída) e implemente um adapter em cima da biblioteca que hoje faz isso. Repita onde a dependência incomoda.\n\n## Relação com outros padrões\n\nNo artigo [Design Patterns: Strategy](/artigos/design-patterns-strategy/), o foco é trocar algoritmos atrás de um contrato. O Adapter isola dependências externas atrás de uma interface própria. Os dois se complementam: Strategy varia comportamento; Adapter traduz o mundo de fora.\n\n---",
      "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
      "keywords": [
        "adapter",
        "regra",
        "negócio",
        "para",
        "interface",
        "banco",
        "serviço",
        "readonly",
        "dependência",
        "contrato"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Pare de ser refém das dependências. Diga olá ao Design Pattern Adapter",
        "description": "Com o Adapter, o serviço depende de um protocolo e os adapters traduzem MySQL, PostgreSQL ou mocks — sem acoplar a regra de negócio ao driver.",
        "excerpt": "Migrar de MySQL para PostgreSQL não precisa reescrever o serviço. O Adapter isola o plugin atrás de um contrato que a regra de negócio entende.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-pt-br.md"
      }
    },
    {
      "id": "bedf6fd069ed901f",
      "url": "https://imrafaeldev.site/experiences/infosistemas-en",
      "title": "Infosistemas | Rafael Pereira",
      "content": "## Context\n\nAt Infosistemas, I worked on platforms for rental companies, fleets, automakers, and mobility operations. It was an enterprise environment of integrations, fiscal flows, and high-volume services, where I worked closely with DevOps, SREs, and DBAs.\n\n## How I worked\n\nMy work combined architecture with hands-on delivery. I redesigned flows between microservices, led NestJS and Go integrations, and implemented critical-event traceability with NestJS and MongoDB. I also evolved APIs, investigated security issues, and contributed to digital journeys and webapp components when backend and interface continuity was needed.\n\nThe principle that guided my work was making failures observable and manageable from the design stage. In messaging, I treated durability, retry, idempotency, and consumer control as part of the flow rather than later fixes.\n\n## What I took from it\n\nThis experience expanded my work in systems with many dependencies and specialists. I learned to turn requirements, risks, and operational constraints into decisions that stayed clear through client validation.",
      "description": "My experience at Infosistemas with messaging, integrations, and mobility platforms.",
      "keywords": [
        "with",
        "worked",
        "work",
        "integrations",
        "flows",
        "nestjs",
        "that",
        "from",
        "context",
        "infosistemas"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "experience-infosistemas",
        "slug": "infosistemas",
        "title": "Infosistemas | Rafael Pereira",
        "description": "My experience at Infosistemas with messaging, integrations, and mobility platforms.",
        "company": "Infosistemas",
        "role": "Senior Software Engineer / Software Architect",
        "period": "Feb 2025 to May 2026",
        "caseSlug": "rabbitmq-messaging",
        "caseSummary": "The case study details how I redesigned RabbitMQ messaging to make critical flows more predictable and reduce intermittent failures between microservices.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/infosistemas-en.md"
      }
    },
    {
      "id": "c0e591742703f77a",
      "url": "https://imrafaeldev.site/projects/goc-mcp-es",
      "title": "goc_mcp",
      "content": "## Problema\n\nUn maestro (Codex o Cursor) necesita delegar trabajo a workers OpenCode con ciclo de vida explícito: planear, iniciar, acompañar, responder, cancelar y recuperar, sin recursión infinita de agentes.\n\n## Restricciones\n\nTodo es local. El daemon autentica en loopback. Hay límite global y por workspace. Las tareas interrumpidas necesitan reconciliarse. La corrupción de estado no puede volverse escritura ciega. En el camino feliz: un worker OpenCode por vez, sin descomposición automática que deje al maestro sin control.\n\n## Decisión\n\nImplementación en Go: gateways MCP por stdio, daemon único, máquina de estados y ejecutor FIFO. Persistencia en SQLite con WAL; artefactos en JSONL. Adaptador OpenCode con servidor como camino principal y CLI como fallback. Aislamiento contra delegación recursiva y diagnóstico solo lectura si el estado se corrompe.\n\n## Estado actual\n\nRepositorio con pruebas, ADRs y benchmarks. La orquestación funcionó. Las mediciones publicadas en el propio proyecto no confirmaron la hipótesis de reducir tiempo y costo.\n\n## Limitaciones\n\nEs un experimento. No afirma ganancia de productividad de ingeniería de agentes en producción. El resultado negativo de la hipótesis forma parte del artefacto.",
      "description": "Orquestación local de agentes vía MCP en Go: maestro, daemon FIFO, workers OpenCode y SQLite. La entrega funcionó, pero la hipótesis de costo y tiempo no se confirmó en esta medición.",
      "keywords": [
        "opencode",
        "estado",
        "maestro",
        "agentes",
        "daemon",
        "camino",
        "como",
        "hipótesis",
        "problema",
        "codex"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "projeto-goc-mcp",
        "slug": "goc-mcp",
        "title": "goc_mcp",
        "description": "Orquestación local de agentes vía MCP en Go: maestro, daemon FIFO, workers OpenCode y SQLite. La entrega funcionó, pero la hipótesis de costo y tiempo no se confirmó en esta medición.",
        "excerpt": "Maestro (Codex/Cursor) delega vía MCP; daemon FIFO y workers OpenCode ejecutan con estado en SQLite. La orquestación funcionó, pero la medición no confirmó reducción de costo o tiempo.",
        "repoUrl": "https://github.com/imrafaeldev/goc_mcp",
        "status": "Experimento documentado",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/goc-mcp-es.md"
      }
    },
    {
      "id": "c10f5a112f992da6",
      "url": "https://imrafaeldev.site/en/projects",
      "title": "Projects (Part 2)",
      "content": "The adaptive interview extracts evidence; the hybrid gateway (LLM + heuristics) blocks invented lived experience. Only confirmed content moves to draft and Markdown or SlideMark export.\n\n[Open the project](/en/projects/post-engine/)\n\nAutoria antes da geração\nEntrevista extrai evidência; briefing e storyboard preparam o material; o gateway veta fabricado. Só o confirmado segue para rascunho e export.\n\n## Other work, other contexts\n\nVisual projects to explore at your own pace.\n\n-\n[Gran Goiás Institutional site for Gran Goiás, a marble workshop with stone execution for large-scale works. Visit site](https://gran-goias.vercel.app/)\n\n-\n[Gabriel | Sports Nutrition Gabriel Pereira's institutional site for sports nutrition, with real content and strategy](https://gabriel-pereira-nutri.vercel.app/)",
      "description": "Artifacts with problem, constraints, and current repository state.",
      "keywords": [
        "projects",
        "export",
        "with",
        "open",
        "project",
        "sqlite",
        "local",
        "diffvision",
        "repository",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/projects"
      }
    },
    {
      "id": "c278edf199c07392",
      "url": "https://imrafaeldev.site/en/articles/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- Design Patterns: Strategy\n\n## Design Patterns: Strategy\n\nIf/else chains grow and turn fragile. Strategy isolates each algorithm behind a contract.\n\nMay 5, 2022\n\n- [Architecture](/en/articles/?topic=arquitetura)\n\nif/else chains grow and turn fragile. The Strategy pattern encapsulates each algorithm in its own class and lets implementations be swapped at runtime without changing the consuming code.\n\n## The problem: endless if/else\n\nA calculator with addition, subtraction, multiplication, and division usually starts as a DefaultCalculator class: private methods per operation and a public function choosing which to invoke with a switch or if/else chain.\n\nclass DefaultCalculator {\npublic calculate(parameters: BinaryOperationParameters): Result {\nconst { operator, firstOperand, secondOperand } = parameters;\n\nswitch (operator) {\ncase \"*\":\nreturn firstOperand * secondOperand;\ncase \"+\":\nreturn firstOperand + secondOperand;\ncase \"-\":\nreturn firstOperand - secondOperand;\ncase \"/\":\nreturn firstOperand / secondOperand;\ncase \"**\":\nreturn firstOperand ** secondOperand;\ncase \"%\":\nreturn firstOperand % secondOperand;\ndefault:\nthrow new Error(\"Operator not found!\");\n}\n}\n}\nThe problem appears when the calculator must cover more binary operations between integers: percent, exponentiation, modulo, bit shifts. Each new feature changes the original implementation, raises coupling, and makes maintenance more expensive.\n\n## What is the Strategy pattern?\n\nThe pattern defines functionality through a contract (interface), implemented according to context. The interface defines the operation; concrete implementations define its execution.\n\nConsuming code depends on the abstraction. Each strategy stays isolated in its own class. New behaviors arrive without changing existing code, aligned with the Open/Closed Principle.\n\n## Defining the contract",
      "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "strategy",
        "return",
        "case",
        "class",
        "operator",
        "parameters",
        "each",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/en/articles/design-patterns-strategy"
      }
    },
    {
      "id": "c29ae38733be9066",
      "url": "https://imrafaeldev.site/en/articles/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 3)",
      "content": "/**\n* The calculate implementation in Calculator does not change per operation.\n* What grows is the ContextAnalyzer, which adds one case per new operation.\n*/\npublic calculate(parameters: BinaryOperationParameters): Result {\nconst { operator, firstOperand, secondOperand } = parameters;\nreturn this.contextAnalyzer\n.getInstance(operator)\n.calculate({ firstOperand, secondOperand });\n}\n}\n\n## Why use Strategy?\n\nStrategy helps in legacy code with several business rules, each represented by an if and a long implementation. The shared part stays in the contract; each rule variation stays in its own class; a context analyzer (resolver/factory) picks the strategy.\n\nOn each request, the code evaluates the operation context and selects the implementation matching the contract.\n\n## Relation to other patterns\n\n- Adapter: Strategy varies behavior; Adapter isolates external dependencies behind an owned interface.\n\n- SOLID (OCP): Strategy is one way to apply the Open/Closed Principle.\n\nStrategy: contrato, seleção e concretas\nCalculator depende do contrato BinaryOperationStrategy. ContextAnalyzer escolhe a concreta (ex.: Sum) pelo operador; Sum, Division e Pow implementam o mesmo contrato.",
      "description": "How the Strategy pattern encapsulates interchangeable algorithms and avoids fragile if/else chains, with a TypeScript calculator example.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "strategy",
        "return",
        "case",
        "class",
        "operator",
        "parameters",
        "each",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/en/articles/design-patterns-strategy"
      }
    },
    {
      "id": "c2e33582705ab87c",
      "url": "https://imrafaeldev.site/es/curriculum",
      "title": "Currículum (Part 4)",
      "content": "Trabajé en sistemas vinculados a laboratorios, investigación y desarrollo, combinando el mantenimiento de un producto existente con su evolución funcional.\n\nContribución\n\nMantuve y evolucioné el sistema con Java, Spring Boot y Angular, implementé pruebas de integración y entregué informes y funcionalidades de extremo a extremo.\n\n[Leer la experiencia completa](/es/experiencias/sustentec/)\n\n- nov/2019 a ene/2021 Campina Grande, Paraíba / Remoto Braistech Ingeniero de Software Full Stack Pleno Expandir experiencia Replegar experiencia\nLideré la estructuración del sistema principal con Node.js y NestJS y diseñé microservicios para el núcleo del negocio.\n\nContexto\n\nEn un producto con contratos de criptoactivos y movimientos financieros, asumí responsabilidad amplia dentro de un equipo pequeño.\n\nContribución\n\nLideré la estructuración del sistema principal con Node.js y NestJS, diseñé microservicios para el núcleo del negocio, ayudé a evolucionar el código hacia Clean Architecture y orienté a desarrolladores junior.\n\n[Leer la experiencia completa](/es/experiencias/braistech/)\n\n## Especialidades\n\n-\n\n### Node.js\n\nEspecialidad en APIs, microservicios e integraciones con NestJS.\n\n-\n\n### Go\n\nExperiencia avanzada en APIs, concurrencia y procesamiento de alto volumen.\n\n-\n\n### Java\n\nExperiencia en mantenimiento y evolución de sistemas con Spring Boot.\n\n-\n\n### React\n\nTrabajo en la evolución y el mantenimiento de aplicaciones React.\n\n-\n\n### Angular\n\nTrabajo en una aplicación web Angular con formularios, validaciones y consumo de API.\n\n## Formación\n\n-\n\n### Análisis y Desarrollo de Sistemas\n\nTecnólogo · UNOPAR — Universidade Norte do Paraná\n\nfinalizado en 2024\n-\n\n### Computación en la Nube\n\nPosgrado · Anhanguera Educacional\n\nfinalizado en 2025\n\n## Enlaces públicos\n\n- [LinkedIn — Abrir enlace público](https://www.linkedin.com/in/this-rafael-pereira/)\n- [GitHub — Abrir enlace público](https://github.com/this-rafael)",
      "description": "Currículum online de Rafael Pereira, ingeniero backend senior con experiencia en Node.js, Go y Java, y trabajo complementario con React y Angular.",
      "keywords": [
        "experiencia",
        "nestjs",
        "para",
        "ingeniero",
        "node",
        "backend",
        "remoto",
        "expandir",
        "replegar",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/es/curriculum"
      }
    },
    {
      "id": "c53f966b6e7c2178",
      "url": "https://imrafaeldev.site/es/articulos/go-intensivo",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 5)",
      "content": "Usa logs estructurados y campos de correlación sin registrar credenciales ni payloads sensibles completos. Mide throughput, errores por clase, p50/p95/p99, lag, edad del evento, retries, DLQ, duplicatas, goroutines, heap y pausas de GC. Usa tracing muestreado para cruzar ingesta, stream y persistencia; trazar cada lectura de alta frecuencia puede costar más de lo que ayuda.\n\nUna data race es acceso concurrente a la misma posición de memoria con al menos una escritura y sin orden de sincronización. Envíos por channel, unlock/lock de mutex y operaciones atómicas establecen relaciones de orden. El detector solo cubre rutas ejecutadas:\n\ngo test -race ./...\ngo test -bench=. -benchmem ./...\ngo tool pprof cpu.out\ngo tool trace trace.out\nG es goroutine, M es hilo del sistema y P es recurso lógico de ejecución. GOMAXPROCS limita cuántos Ps ejecutan código Go en",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "return",
        "context",
        "puede",
        "orden",
        "channel",
        "concurrencia",
        "más",
        "value"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/go-intensivo"
      }
    },
    {
      "id": "c5780107b8ce4a67",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 3)",
      "content": "Watch out: a usecase that only delegates to the protocol without validating may be pushing business rules into the adapter. In `CreateUserUsecase`, the duplicate-name check is Core's obligation.\n\n### Exceptions\n\n`UserAlreadyExistsException` belongs to Core: an invalid rule flow is also a rule. Each failure mapped to a known exception helps maintenance. Base with `code` (later becomes HTTP status at the edge):\n\n```typescript\n// core/exceptions/IBaseException.ts\nexport abstract class IBaseException extends Error {\n  code: number;\n\n  constructor(message: string) {\n    super(message);\n  }\n}\n```\n\n```typescript\n// core/exceptions/UserAlreadyExistsException.ts\nexport class UserAlreadyExistsException extends IBaseException {\n  constructor(message?: string) {\n    super(message ?? \"User already exists\");\n    this.code = 400;\n  }\n}\n```\n\nThe usecase **throws** exceptions; it does **not** handle them. Mapping unknown type → known type belongs in adapter or infra.\n\n### Protocols\n\n`CreateUserProtocol` and `GetUserByNameProtocol` are contracts for external-device access. A protocol exists to inform or trigger external action — **not** to process business rules. Preference: one public method per protocol.\n\n```typescript\n// core/protocols/CreateUserProtocol.ts\nexport abstract class CreateUserProtocol {\n  abstract register(name: string): Promise<UserEntity>;\n}\n```\n\n```typescript\n// core/protocols/GetUserByNameProtocol.ts\nexport abstract class GetUserByNameProtocol {\n  abstract getByName(name: string): Promise<UserEntity | null>;\n}\n```\n\nCore is the center; the adaptation layer connects the rest.\n\n## Adapter\n\nAdapters control bidirectional traffic: external → rule and rule → external. They adapt objects, parameters, and exceptions — the same spirit as the [Adapter pattern](/en/articles/design-patterns-adapter/).\n\nTwo groups:\n\n1. Called by Core — implement at least one protocol.\n2. Called by Infra — generally **services**.\n\n### Connectors, handlers, and repositories",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "c666651046a0782f",
      "url": "https://imrafaeldev.site/en/articles/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 1)",
      "content": "- [Home](/en/)\n- [Articles](/en/articles/)\n- TypeScript Clean Architecture: Core, Adapters, and Infra\n\n## TypeScript Clean Architecture: Core, Adapters, and Infra\n\nWeak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.\n\nMarch 15, 2023\n\n- [Architecture](/en/articles/?topic=arquitetura)\n\nSoftware development changes all the time. Weak architecture becomes expensive maintenance, slow features, hard tests, and bugs that are hard to isolate. It is worth investing in a structure that supports evolution without rewriting the system at every business pressure.\n\n## A bit of history\n\nClean Architecture is the name Robert C. Martin (Uncle Bob) gave, in 2012, in the book *Clean Architecture: A Craftsman’s Guide to Software Structure and Design*. The proposal avoids the rigidity of architectures coupled to framework and database: the core stays stable; external details change. The idea draws from DDD, SOLID, Onion Architecture, and Hexagonal Architecture.\n\n## General proposal\n\nThis article describes Clean Architecture and a practical derivation for TypeScript backends: three layers — **Core**, **Adapters**, and **Infra**.\n\n- **Core** — business rules and domain entities. Innermost layer.\n\n- **Infra** — external connections: concrete repositories, REST controllers, DI modules, framework boilerplate.\n\n- **Adapters** — mediation in both directions. A controller does not call a “raw” usecase: it goes through a service. A usecase does not talk to the database: it talks to a protocol that an adapter (repository, connector, handler) implements.\n\nEach layer has different capabilities and constraints; SOLID weighs more in Core. It works for HTTP CRUD and for systems with several frameworks and channels.\n\nConcrete benefits: clear responsibilities (reading and maintenance), flexibility to swap plugins without rewriting rules, and isolated tests per layer.",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "architecture",
        "return",
        "export",
        "class",
        "adapters"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/en/articles/typescript-clean-architecture"
      }
    },
    {
      "id": "c732781609842190",
      "url": "https://imrafaeldev.site/artigos/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 2)",
      "content": "O primeiro passo é a interface do contrato da estratégia. Na calculadora, algo que receba dois números e retorne o resultado:\n\ninterface BinaryOperationParameters {\nfirstOperand: number;\nsecondOperand: number;\noperator: string;\n}\n\ntype Result = number;\n\ninterface BinaryOperationStrategy {\ncalculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result;\n}\n\n## Implementando estratégias concretas\n\nCada operação matemática vira uma classe que implementa BinaryOperationStrategy e executa uma única operação. Soma e divisão:\n\nclass Sum implements BinaryOperationStrategy {\npublic calculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result {\nconst { firstOperand, secondOperand } = parameters;\nreturn firstOperand + secondOperand;\n}\n}\n\nclass Division implements BinaryOperationStrategy {\npublic calculate(\nparameters: Pick&#x3C;\nBinaryOperationParameters,\n\"firstOperand\" | \"secondOperand\"\n>,\n): Result {\nconst { firstOperand, secondOperand } = parameters;\nif (secondOperand === 0) {\nthrow new Error(\"Division by zero is not allowed!\");\n}\nreturn firstOperand / secondOperand;\n}\n}\n\n## O Context e a Factory\n\nPara amarrar as estratégias, entram um Context e uma Factory (ou Analyzer). Em ContextAnalyzer, um método avalia o operador e retorna a Strategy correta:\n\nclass ContextAnalyzer {\npublic getInstance(operator: string): BinaryOperationStrategy {\nswitch (operator) {\ncase \"*\":\nreturn new Multiplication();\ncase \"+\":\nreturn new Sum();\ncase \"-\":\nreturn new Subtraction();\ncase \"/\":\nreturn new Division();\ncase \"%\":\nreturn new Percent();\ncase \"**\":\nreturn new Pow();\ndefault:\nthrow new Error(\"Operator not found!\");\n}\n}\n}\nO contexto recebe e executa a estratégia. Ele conhece apenas o contrato, não a implementação concreta. A antiga DefaultCalculator passa a receber esse ContextAnalyzer por injeção:\n\nclass Calculator {\nconstructor(private readonly contextAnalyzer: ContextAnalyzer) {}",
      "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "contrato",
        "cada",
        "parameters",
        "operator",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "/artigos/design-patterns-strategy"
      }
    },
    {
      "id": "c82e6e2769b8654c",
      "url": "https://imrafaeldev.site/articles/desenoxidando-logica-container-with-most-water-es",
      "title": "Desenoxidando la lógica #02: Container With Most Water (Part 2)",
      "content": "Cuando los punteros están en las posiciones `left` y `right`, mover el puntero de la mayor altura no puede aumentar la altura mínima del recipiente. El ancho siempre disminuye, y la altura que limita el área sigue presente.\n\nPor eso, el puntero que debe avanzar es el de la menor altura. Es el único movimiento que puede encontrar una línea más alta y compensar la pérdida de ancho.\n\nSi las alturas son iguales, cualquiera de los dos puede avanzar. En el código, elegí avanzar el de la izquierda cuando `heights[leftIndex] <= heights[rigthIndex]`.\n\n## Solución con dos punteros\n\n```javascript\n/**\n * @param {number[]} heights\n * @return {number}\n */\nvar maxArea = function (heights) {\n  let leftIndex = 0;\n  let rigthIndex = heights.length - 1;\n  let maxArea = 0;\n\n  while (leftIndex < rigthIndex) {\n    const minH = Math.min(heights[leftIndex], heights[rigthIndex]);\n    const currentArea = minH * (rigthIndex - leftIndex);\n\n    maxArea = Math.max(maxArea, currentArea);\n\n    if (heights[leftIndex] <= heights[rigthIndex]) {\n      leftIndex++;\n    } else {\n      rigthIndex--;\n    }\n  }\n\n  return maxArea;\n};\n```\n\nEn cada ronda, calculo el área del par actual, actualizo la mayor área encontrada y muevo uno de los punteros. El `while` se aproxima al centro y termina.\n\nEl detalle del avance importa. En la versión que yo había escrito, los punteros solo avanzaban cuando el área actual no era mayor que `maxArea`. Si se encontraba una nueva área máxima, la misma combinación se calculaba de nuevo, sin salir del loop. La corrección fue separar las dos decisiones: registrar el área y, después, mover el puntero de la menor altura.\n\n## El resultado de los intentos\n\nEl historial de LeetCode quedó así:\n\n- JavaScript: aceptada, `3 ms` y `63.6 MB`.\n- JavaScript: respuesta incorrecta.\n- JavaScript: respuesta incorrecta.\n- Go: aceptada, `0 ms` y `9.6 MB`.\n- TypeScript: aceptada, `3 ms` y `63.9 MB`.\n- Go: respuesta incorrecta.",
      "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
      "keywords": [
        "área",
        "heights",
        "leftindex",
        "rigthindex",
        "altura",
        "punteros",
        "maxarea",
        "javascript",
        "leetcode",
        "mayor"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "desenoxidando-logica-container-with-most-water",
        "title": "Desenoxidando la lógica #02: Container With Most Water",
        "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
        "excerpt": "Intenté elegir el próximo paso mirando solo a los vecinos. Funcionó en algunos casos, pero el problema pedía una visión más amplia.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/desenoxidando-logica-container-with-most-water-es.md"
      }
    },
    {
      "id": "c82f7a32a13decd7",
      "url": "https://imrafaeldev.site/projects/diffvision-pt-br",
      "title": "DiffVision",
      "content": "## Problema\n\nRevisar um diff Git em ferramenta SaaS envia código para fora e mistura UI remota com o histórico local. A revisão precisa funcionar offline, com hunks, filtros, bookmarks e comentários ancorados em linhas.\n\n## Restrições\n\nPreferências e relatórios devem viver no próprio repositório (`.diffvision/`). A CLI npm inicia backend e UI locais. Integração com assistente não pode ser vendida como pronta se ainda for protótipo.\n\n## Decisão\n\nCLI inspeciona o Git, interpreta diff unificado e sobe interface web. Backend Fastify com snapshot e WebSocket; UI React/Vite. Exportação Markdown/JSON no repositório. Pacote `diffvision-mcp` por stdio para resumir repositório, ler patches e registrar comentários. O assistente visual de revisão por IA é declarado mock/protótipo; a escrita de comentários via MCP é funcional.\n\n## Estado atual\n\nDistribuído como CLI npm, com execução local-first.\n\n## Limitações\n\nNão substitui o fluxo de review do GitHub. O fluxo de IA visual não deve ser lido como produto acabado.",
      "description": "CLI npm local-first para revisar diffs Git, com UI local, comentários no repositório, exportação em Markdown/JSON e servidor MCP. A revisão visual por IA permanece mock.",
      "keywords": [
        "comentários",
        "repositório",
        "não",
        "como",
        "diff",
        "para",
        "revisão",
        "backend",
        "assistente",
        "protótipo"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "projeto-diffvision",
        "slug": "diffvision",
        "title": "DiffVision",
        "description": "CLI npm local-first para revisar diffs Git, com UI local, comentários no repositório, exportação em Markdown/JSON e servidor MCP. A revisão visual por IA permanece mock.",
        "excerpt": "O diff Git abre na UI local; comentários e exportação em Markdown ficam no repositório. A revisão visual por IA permanece mock.",
        "repoUrl": "https://github.com/imrafaeldev/diffvision-app",
        "status": "CLI pública; revisão por IA ainda mock",
        "featured": "false",
        "order": "4",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/diffvision-pt-br.md"
      }
    },
    {
      "id": "c86c977c3fe39dfd",
      "url": "https://imrafaeldev.site/es/articulos/backend-performance-cerca-de-los-datos",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- La mayoría de los problemas de performance de backend empieza cerca de los datos\n\n## La mayoría de los problemas de performance de backend empieza cerca de los datos\n\nAPI lenta y la reunión ya se llena de soluciones. Yo empiezo cerca de los datos — no por culpar a la base, sino porque esa verificación suele dar señal rápida.\n\n16 de julio de 2026\n\n- [Rendimiento](/es/articulos/?topic=performance)\n- [Arquitectura](/es/articulos/?topic=arquitetura)\n\nUna API se vuelve lenta y la reunión ya se llena de soluciones antes de ganar una medición. Aparecen más CPU y RAM, caché, cola, microservicios y refactorización. A veces alguien propone hasta cambiar el lenguaje. Raramente la primera sugerencia es abrir el plan de ejecución.\n\nPor eso, empiezo la investigación cerca de los datos. No porque la base sea la culpable por defecto, sino porque mucho pasa por ahí y esa verificación suele dar señal rápida. Si la consulta está saludable, saco la base del frente y sigo el flujo de la petición.\n\n## Soluciones rápidas también esconden trabajo caro\n\nCaché y cola resuelven problemas reales. Más máquina también puede ser la decisión correcta. Cuando entran por reflejo, sin embargo, esos recursos pueden solo cambiar el tamaño de la cuenta mientras nadie sabe qué tramo de la petición está reteniendo la respuesta.\n\nSubir CPU reduce la disputa por recurso, la caché saca algunas peticiones del camino y la cola absorbe un pico. Mientras tanto, una consulta que hace un trabajo enorme para devolver casi nada sigue cara en cada ejecución. Lo mismo vale para un loop que dispara una llamada por ítem.\n\nLa latencia puede caer por un tiempo, la alerta deja de sonar y el equipo respira. Cuando la carga crece, la operación cara reaparece, ahora acompañada de una infraestructura mayor.\n\nLa pregunta que debe venir antes de la discusión de arquitectura: ¿cuánto trabajo está produciendo esta petición para entregar el resultado?",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "base",
        "consulta",
        "para",
        "plan",
        "puede",
        "antes",
        "ejecución",
        "datos",
        "trabajo",
        "cuando"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/backend-performance-cerca-de-los-datos"
      }
    },
    {
      "id": "c86dba984a25bf90",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 6)",
      "content": "**Quando escolher outra abordagem.** Se a ordem de conclusão importar ou os erros precisarem encerrar o lote, use `errgroup` com limite em vez de `WaitGroup` manual. Se o produtor é muito mais rápido que o consumidor de forma sustentada, limitar concorrência não basta: adicione descarte com `select`/`default`, fila persistente ou controle de admissão na borda (limite de taxa, circuit breaker). Para I/O com latência dominada por espera, o teto pode ser maior que o número de CPUs; para CPU-bound, mantenha próximo de `GOMAXPROCS`.\n\n## 5. Consumidores resilientes: ACK, idempotência, retry e ordem por chave\n\n**Mecanismo.** Um consumidor resiliente separa *receber*, *processar* e *confirmar* (ACK). A mensagem só recebe ACK após o efeito ser durável; o processamento é idempotente (repetir a mesma mensagem produz o mesmo estado); falhas transitórias usam retry com backoff e limite de tentativas; falhas persistentes vão para uma fila de mensagens mortas (DLQ); e a ordem só é garantida dentro de uma chave de partição, processada por um único worker por vez.\n\n**Falha ou limite que ele trata.** Sem esse desenho, três falhas clássicas aparecem: confirmar antes de processar perde mensagens em caso de queda; processar sem idempotência duplica efeitos (cobrança, envio, escrita) quando o broker reentrega; reprocessar sem DLQ trava o consumidor em mensagem envenenada. O limite: garantia global de ordem e exatamente-uma-vez de ponta a ponta não existem em sistemas distribuídos práticos — o desenho entrega *ordem por chave* e *efeito único via idempotência*.\n\n**Exemplo de aplicação.** Cenário didático e simulação sequencial e volátil: mapa em memória, DLQ em slice e ACK lógico não têm durabilidade e não demonstram particionamento — o campo `Key` é apenas um rótulo, sem worker por partição. Serve só para treinar a sequência receber → processar → confirmar.\n\n```go\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "cb04164a42a1ecc3",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-es",
      "title": "Design Patterns: Strategy (Part 2)",
      "content": "```typescript\ninterface BinaryOperationParameters {\n  firstOperand: number;\n  secondOperand: number;\n  operator: string;\n}\n\ntype Result = number;\n\ninterface BinaryOperationStrategy {\n  calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result;\n}\n```\n\n## Implementando estrategias concretas\n\nCada operación matemática se vuelve una clase que implementa `BinaryOperationStrategy` y ejecuta una única operación. Suma y división:\n\n```typescript\nclass Sum implements BinaryOperationStrategy {\n  public calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result {\n    const { firstOperand, secondOperand } = parameters;\n    return firstOperand + secondOperand;\n  }\n}\n\nclass Division implements BinaryOperationStrategy {\n  public calculate(\n    parameters: Pick<\n      BinaryOperationParameters,\n      \"firstOperand\" | \"secondOperand\"\n    >,\n  ): Result {\n    const { firstOperand, secondOperand } = parameters;\n    if (secondOperand === 0) {\n      throw new Error(\"Division by zero is not allowed!\");\n    }\n    return firstOperand / secondOperand;\n  }\n}\n```\n\n## El Context y la Factory\n\nPara amarrar las estrategias, entran un Context y una Factory (o Analyzer). En `ContextAnalyzer`, un método evalúa el operador y retorna la Strategy correcta:\n\n```typescript\nclass ContextAnalyzer {\n  public getInstance(operator: string): BinaryOperationStrategy {\n    switch (operator) {\n      case \"*\":\n        return new Multiplication();\n      case \"+\":\n        return new Sum();\n      case \"-\":\n        return new Subtraction();\n      case \"/\":\n        return new Division();\n      case \"%\":\n        return new Percent();\n      case \"**\":\n        return new Pow();\n      default:\n        throw new Error(\"Operator not found!\");\n    }\n  }\n}\n```",
      "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "parameters",
        "operator",
        "strategy",
        "cada",
        "operación",
        "calculate"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
        "excerpt": "Las cadenas de if/else crecen y se vuelven frágiles. Strategy aísla cada algoritmo tras un contrato.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-es.md"
      }
    },
    {
      "id": "cb4f67dad093a0a3",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-en",
      "title": "TypeScript Clean Architecture: Core, Adapters, and Infra (Part 2)",
      "content": "```typescript\n// core/entities/UserEntity.ts\nexport interface UserEntityProps {\n  id?: string;\n  name: string;\n}\n\nexport class UserEntity {\n  constructor(private readonly props: UserEntityProps) {}\n\n  get id(): string {\n    return this.props.id ?? \"\";\n  }\n\n  get name(): string {\n    return this.props.name;\n  }\n}\n```\n\nThe entity receives typed props and exposes getters. It depends on an interface any transfer DTO can satisfy later.\n\n### Features and usecases\n\nCRUD needs create, fetch, update, and remove. In Core, each usecase implements a contract (feature) with a single public method — aligned with Liskov, open/closed, interface segregation, and single responsibility. The usecase does **not** access the database: it knows **protocols** describing the external action (dependency inversion).\n\nRegistration: name is required; if it already exists, error; otherwise return `UserEntity`.\n\n- contract `CreateUser`\n- implementation `CreateUserUsecase`\n\nIn TypeScript, an abstract class with abstract methods works as both contract *and* value — useful for DI (`const createUserSymbol = CreateUser`):\n\n```typescript\n// core/features/CreateUser.ts\nexport abstract class CreateUser {\n  abstract execute(name: string): Promise<UserEntity>;\n}\n```\n\n```typescript\n// core/usecases/CreateUserUsecase.ts\nexport class CreateUserUsecase implements CreateUser {\n  constructor(\n    private readonly createUserProtocol: CreateUserProtocol,\n    private readonly getByNameProtocol: GetUserByNameProtocol,\n  ) {}\n\n  async execute(name: string): Promise<UserEntity> {\n    const existsName = await this.getByNameProtocol.getByName(name);\n\n    if (existsName) {\n      throw new UserAlreadyExistsException(\n        `the name ${name} already exists`,\n      );\n    }\n\n    return this.createUserProtocol.register(name);\n  }\n}\n```\n\nThe usecase defines *what* (validate name, register). It does not define *how* to fetch or persist. The rule stays independent of lib, framework, and database.",
      "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
      "keywords": [
        "string",
        "name",
        "userentity",
        "class",
        "export",
        "return",
        "private",
        "user",
        "resolve",
        "with"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-clean-architecture",
        "title": "TypeScript Clean Architecture: Core, Adapters, and Infra",
        "description": "A Clean Architecture derivation for TypeScript backends: Core with usecases and protocols, bidirectional Adapters, and NestJS Infra with dependency injection.",
        "excerpt": "Weak architecture blocks maintenance, testing, and change. This Clean Architecture derivation for TypeScript backends separates Core, Adapters, and Infra — with dependencies pointing inward.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-en.md"
      }
    },
    {
      "id": "cb55d14315d4caa4",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-pt-br",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 5)",
      "content": "Depois desse caso, a regra que ficou para mim é esta: se o problema é esperar muita coisa ao mesmo tempo, Node.js com Event Loop tende a orquestrar muito bem. Se o problema é calcular muita coisa ao mesmo tempo, eu considero Go com goroutines mais cedo.\n\nMas eu também passei a desconfiar de migração que promete resolver tudo. Trocar tecnologia pode só deslocar o gargalo.\n\nNo meu caso, melhorou CPU e tempo, mas obrigou a reanalisar memória e chunking.\n\nNo fim, a briga entre goroutines e Event Loop é menos sobre qual modelo é superior e mais sobre qual gargalo você está tentando atacar.\n\nPara I/O, o balcão da bodega funciona muito bem. Para CPU, às vezes você precisa mesmo colocar mais gente na cozinha da festa.\n\n---\n\n*Publicado originalmente no [LinkedIn](https://www.linkedin.com/pulse/goroutines-vs-event-loop-compara%C3%A7%C3%A3o-errada-entre-dois-s-pereira-qtgle/) em 24 de junho de 2026.*",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "mais",
        "mesmo",
        "tempo",
        "trabalho",
        "loop",
        "problema",
        "pode"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência",
        "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
        "excerpt": "Node.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-pt-br.md"
      }
    },
    {
      "id": "cd08e3b61fbb05ba",
      "url": "https://imrafaeldev.site/artigos/desenferrujando-logica-group-anagrams",
      "title": "Desenferrujando a lógica #01: Group Anagrams (Part 3)",
      "content": "func groupAnagrams(strs []string) [][]string {\ngroups := make(map[[26]uint8][]string, len(strs))\n\nfor _, str := range strs {\nvar key [26]uint8\n\nfor i := 0; i &#x3C; len(str); i++ {\nkey[str[i]-'a']++\n}\n\ngroups[key] = append(groups[key], str)\n}\n\nresult := make([][]string, 0, len(groups))\n\nfor _, group := range groups {\nresult = append(result, group)\n}\n\nreturn result\n}\n\n## O que mudou em complexidade?\n\n- Ordenação: O(k log k) por string e O(n × k log k) para n strings.\n\n- Contagem: O(k) por string e O(n × k) para n strings.\n\nA melhoria apareceu quando percebi que a ordenação fazia um trabalho que o problema não exigia. A mudança principal é de O(k log k) para O(k) por string.\n\n## A primeira solução não estava errada\n\nEla resolve o problema e, dependendo do contexto, poderia ser suficiente. O hábito que eu queria recuperar era continuar pensando depois que o código começa a funcionar. Encontrar uma solução, voltar ao problema e perguntar: que trabalho meu algoritmo está fazendo sem precisar?\n\nNão estou fazendo esses exercícios porque LeetCode representa todo o trabalho de engenharia de software, nem para disputar a solução mais sofisticada. Estou fazendo porque percebi que usar IA todos os dias reduziu a quantidade de vezes em que preciso insistir sozinho em um problema. Quero reservar espaço para exercitar isso novamente: ler, tentar, errar, revisar a abordagem e só depois comparar caminhos.\n\n---\n\n*Série Desenferrujando a lógica #01 — Group Anagrams. Adaptado do carousel autoral; problema em [leetcode.com/problems/group-anagrams](https://leetcode.com/problems/group-anagrams/).*\n\nChave por sort e mapa\nCada string vira chave ordenada (eat → aet). O mapa agrupa anagramas sob a mesma chave sem comparar cada par.\n\nContagem [26]uint8 vs sort\nVetor de frequências a–z vira chave. Contagem custa O(k) por string; sort custava O(k log k). Total: de O(n × k log k) para O(n × k).",
      "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
      "keywords": [
        "string",
        "para",
        "chave",
        "cada",
        "group",
        "não",
        "solução",
        "result",
        "problema",
        "sort"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/artigos/desenferrujando-logica-group-anagrams"
      }
    },
    {
      "id": "cd9b15800f21ef61",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-group-anagrams",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 2)",
      "content": "for _, str := range strs {\nsortedStr := sortString(str)\nmapping[sortedStr] = append(mapping[sortedStr], str)\n}\n\nresult := make([][]string, 0, len(mapping))\n\nfor _, group := range mapping {\nresult = append(result, group)\n}\n\nreturn result\n}\nLo que ocurre en ese código:\n\n- sortString(str) transforma el string en bytes, ordena los caracteres y devuelve un nuevo string.\n\n- sortedStr := sortString(str) produce la clave de aquel término.\n\n- mapping[sortedStr] = append(...) usa esa clave para acumular los anagramas en el mismo grupo.\n\n- Para eat, tea y ate, sortedStr será siempre aet.\n\nEl mapa empieza a quedar así:\n\n\"aet\" -> [\"eat\", \"tea\", \"ate\"]\n\"ant\" -> [\"tan\", \"nat\"]\n\"abt\" -> [\"bat\"]\nCalculo una clave y añado la palabra directamente al grupo correspondiente. El map evita comparar cada string con todas las demás.\n\n## ¿Dónde está el costo de ese enfoque?\n\nPara descubrir la clave, necesito ordenar cada string. Si un string tiene k caracteres, esa ordenación cuesta aproximadamente O(k log k). Repitiendo para n strings, la parte dominante queda en O(n × k log k).\n\n### ¿Por qué quitar el sort?\n\nPara eat, ordenaba los caracteres para llegar a aet. Para tea, hacía otra ordenación para llegar al mismo aet. El sort funciona porque crea una representación común. Solo que el problema no exige ordenar nada.\n\nPara saber si dos strings son anagramas, basta verificar si poseen la misma cantidad de cada letra.\n\n## Esa observación cambia la solución\n\nEn lugar de colocar las letras en el mismo orden, puedo contar cuántas veces aparece cada una. En este ejercicio, las entradas usan letras minúsculas de a a z. Cada string puede representarse por 26 contadores.\n\ntea y ate producen el mismo conteo. No necesito reorganizar ningún carácter. Solo recorro el string y cuento las ocurrencias. Para eat, la parte relevante queda:\n\na = 1\ne = 1\nt = 1\n\n### Solución usando conteo",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "group",
        "clave",
        "solución",
        "result",
        "problema",
        "sort",
        "groups"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/es/articulos/desenoxidando-logica-group-anagrams"
      }
    },
    {
      "id": "ce15c1fea6ddc12e",
      "url": "https://imrafaeldev.site/articles/go-intensivo-es",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 6)",
      "content": "- [Go 1.26 Release Notes](https://go.dev/doc/go1.26)\n- [The Go Memory Model](https://go.dev/ref/mem)\n- [Go Concurrency Patterns: Context](https://go.dev/blog/context)\n- [Go Concurrency Patterns: Pipelines and cancellation](https://go.dev/blog/pipelines)\n- [Package errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup)\n- [Data Race Detector](https://go.dev/doc/articles/race_detector)\n- [Diagnostics and profiling](https://go.dev/doc/diagnostics)\n- [Package log/slog](https://pkg.go.dev/log/slog)\n- [Kubernetes probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes)",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "orden",
        "channel",
        "context",
        "https",
        "return",
        "cada",
        "más",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-go-intensive",
        "slug": "go-intensivo",
        "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos",
        "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
        "excerpt": "Una guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 6,
        "sourcePath": "articles/go-intensivo-es.md"
      }
    },
    {
      "id": "ce2707e6056917c4",
      "url": "https://imrafaeldev.site/en/experience/sustentec",
      "title": "Sustentec",
      "content": "- [Home](/en/)\n- Sustentec\n\n## Sustentec\n\nMy experience at Sustentec with research systems, APIs, and quality.\n\nRole\n\nMid-level Full Stack Software Engineer\n\nPeriod\n\nJan 2021 to Aug 2021\n\n## Context\n\nAt Sustentec, I worked on systems connected to laboratories, research, and development. The work combined maintaining an existing product with evolving its features and integrations.\n\n## How I worked\n\nI developed a REST API in Dart with Shelf to integrate research-institution databases. I also maintained and evolved a laboratory management system with Java, Spring Boot, JPA, Hibernate, PostgreSQL, and Angular.\n\nI added integration tests where that coverage did not exist, developed reports and end-to-end features, and took part in gathering requirements with clients and refining sprints with the Product Owner.\n\n## What I took from it\n\nThis experience reinforced that quality is not limited to unit tests. In a system with several layers, I needed to validate the actual behavior across API, persistence, and interface.",
      "description": "My experience at Sustentec with research systems, APIs, and quality.",
      "keywords": [
        "with",
        "sustentec",
        "2021",
        "experience",
        "research",
        "systems",
        "quality",
        "worked",
        "product",
        "features"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/sustentec"
      }
    },
    {
      "id": "cedbd1c51febefa7",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-container-with-most-water-pt-br",
      "title": "Desenferrujando a lógica #02: Container With Most Water (Part 2)",
      "content": "Quando os ponteiros estão nas posições `left` e `right`, mover o ponteiro da maior altura não pode aumentar a altura mínima do recipiente. A largura sempre diminui, e a altura que limita a área continua presente.\n\nPor isso, o ponteiro que deve avançar é o da menor altura. É o único movimento que pode encontrar uma linha mais alta e compensar a perda de largura.\n\nSe as alturas forem iguais, qualquer um dos dois pode avançar. No código, escolhi avançar o da esquerda quando `heights[leftIndex] <= heights[rigthIndex]`.\n\n## Solução com dois ponteiros\n\n```javascript\n/**\n * @param {number[]} heights\n * @return {number}\n */\nvar maxArea = function (heights) {\n  let leftIndex = 0;\n  let rigthIndex = heights.length - 1;\n  let maxArea = 0;\n\n  while (leftIndex < rigthIndex) {\n    const minH = Math.min(heights[leftIndex], heights[rigthIndex]);\n    const currentArea = minH * (rigthIndex - leftIndex);\n\n    maxArea = Math.max(maxArea, currentArea);\n\n    if (heights[leftIndex] <= heights[rigthIndex]) {\n      leftIndex++;\n    } else {\n      rigthIndex--;\n    }\n  }\n\n  return maxArea;\n};\n```\n\nA cada rodada, calculo a área do par atual, atualizo a maior área encontrada e movo um dos ponteiros. O `while` se aproxima do centro e termina.\n\nO detalhe do avanço importa. Na versão que eu havia escrito, os ponteiros só avançavam quando a área atual não era maior que `maxArea`. Se uma nova área máxima fosse encontrada, a mesma combinação seria calculada de novo, sem sair do loop. A correção foi separar as duas decisões: registrar a área e, depois, mover o ponteiro da menor altura.\n\n## O resultado das tentativas\n\nO histórico do LeetCode ficou assim:\n\n- JavaScript: aceita, `3 ms` e `63.6 MB`.\n- JavaScript: resposta incorreta.\n- JavaScript: resposta incorreta.\n- Go: aceita, `0 ms` e `9.6 MB`.\n- TypeScript: aceita, `3 ms` e `63.9 MB`.\n- Go: resposta incorreta.",
      "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
      "keywords": [
        "área",
        "heights",
        "leftindex",
        "rigthindex",
        "altura",
        "ponteiros",
        "maxarea",
        "para",
        "dois",
        "javascript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "desenferrujando-logica-container-with-most-water",
        "title": "Desenferrujando a lógica #02: Container With Most Water",
        "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
        "excerpt": "Eu tentei escolher o próximo passo olhando apenas para os vizinhos. Funcionou em alguns casos, mas o problema pedia uma visão mais ampla.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-container-with-most-water-pt-br.md"
      }
    },
    {
      "id": "cf986d78ec813af5",
      "url": "https://imrafaeldev.site/curriculo",
      "title": "Currículo (Part 2)",
      "content": "- mar/2025 a jun/2025 Remoto Azify Engenheiro Backend Sênior — Consultoria Expandir experiência Recolher experiência\nDesenvolvi um motor de liquidação em NestJS e reduzi em 30% a latência de APIs financeiras críticas por meio de profiling e otimização.\n\nContexto\n\nAtuei em fintech e criptoativos, em serviços financeiros nos quais consistência, segurança e estabilidade tinham impacto direto na operação.\n\nContribuição\n\nDesenvolvi um motor de liquidação em NestJS com integrações financeiras e reduzi em 30% a latência de APIs críticas por meio de profiling e otimização.\n\n[Ler experiência completa](/experiencias/azify/)\n\n- out/2023 a fev/2025 Remoto VBET Engenheiro Backend Sênior Expandir experiência Recolher experiência\nApliquei Go, goroutines e channels para reduzir a primeira etapa do cálculo de comissões de cerca de sete para três minutos, além de atuar na evolução da aplicação React.\n\nContexto\n\nO produto de analytics atendia influenciadores e afiliados de iGaming; o dashboard reunia métricas de comissão e aceitava pequena defasagem para dados do dia, enquanto pagamentos exigiam dados consolidados.\n\nContribuição\n\nReestruturei a API e o caminho de cálculo com Go, goroutines e channels, introduzi ETL, pré-cálculo e cache e colaborei na evolução da aplicação React. A linha medida foi de cerca de sete minutos no pico para cerca de três minutos, cerca de um minuto, aproximadamente 15 segundos sem cache e menos de um segundo com cache quente, conforme cada etapa.\n\n[Ler experiência completa](/experiencias/vbet/)\n\n- abr/2023 a out/2023 Remoto Maxmilhas Engenheiro de Software Full Stack Expandir experiência Recolher experiência\nDesenvolvi microsserviços em Node.js e NestJS para automatizar cancelamentos e remarcações, reduzindo em 34% a intervenção manual do suporte.\n\nContexto\n\nAtuei em fluxos de pós-venda de viagens, incluindo cancelamentos, remarcações, cupons e comunicação com clientes.\n\nContribuição",
      "description": "Currículo online de Rafael Pereira, engenheiro backend sênior com experiência em Node.js, Go e Java, e atuação complementar em React e Angular.",
      "keywords": [
        "experiência",
        "para",
        "nestjs",
        "engenheiro",
        "node",
        "backend",
        "remoto",
        "expandir",
        "recolher",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/curriculo"
      }
    },
    {
      "id": "d12e32a117cf73bd",
      "url": "https://imrafaeldev.site/artigos/intensivao-golang-avancado",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 4)",
      "content": "**Mecanismo.** Channels transferem *posse de dados* entre goroutines e sincronizam remetente e receptor; sync.Mutex (e sync.RWMutex) protegem *acesso a estado compartilhado*. A regra prática: use channels para orquestrar (sinalizar conclusão, distribuir tarefas, aplicar backpressure) e mutex para guardar invariantes de uma estrutura acessada por várias goroutines (contadores, caches, mapas).\n\n**Falha ou limite que ele trata.** Channels evitam condição de corrida por construção quando o dado atravessa o canal em vez de ser compartilhado. O limite: modelar todo estado compartilhado com uma goroutine “dona” e canais de pedido/resposta adiciona latência, complexidade e risco de deadlock quando um simples mutex resolveria. Inversamente, proteger um pipeline inteiro com um único mutex gigante serializa trabalho que poderia fluir em paralelo.\n\n**Exemplo de aplicação.** Cenário didático: o mesmo contador implementado das duas formas para comparar.\n\npackage main\n\nimport (\n\"fmt\"\n\"sync\"\n)\n\n// Com mutex: direto para estado compartilhado simples.\ntype Counter struct {\nmu sync.Mutex\nn int\n}\n\nfunc (c *Counter) Inc() {\nc.mu.Lock()\ndefer c.mu.Unlock()\nc.n++\n}\n\n// Com channel: a goroutine dona centraliza as atualizações.\nfunc runCounterOwner(increments int) int {\ninc := make(chan struct{})\ndone := make(chan int)\n\ngo func() {\ntotal := 0\nfor range inc {\ntotal++\n}\ndone &#x3C;- total\n}()\n\nvar wg sync.WaitGroup\nfor i := 0; i &#x3C; increments; i++ {\nwg.Add(1)\ngo func() {\ndefer wg.Done()\ninc &#x3C;- struct{}{}\n}()\n}\nwg.Wait()\nclose(inc)\nreturn &#x3C;-done\n}",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "goroutines",
        "sync",
        "func",
        "contexto",
        "quando",
        "done",
        "mutex",
        "limite",
        "context"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-golang-avancado"
      }
    },
    {
      "id": "d185f71ed0db5e9d",
      "url": "https://imrafaeldev.site/en/articles/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 5)",
      "content": "But I also grew suspicious of migrations promising to fix everything. Switching technology may only move the bottleneck.\n\nIn my case, CPU and time improved, but memory and chunking had to be reanalyzed.\n\nIn the end, the goroutines vs Event Loop fight is less about which model",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "loop",
        "event",
        "problem",
        "work",
        "goroutines",
        "same",
        "concurrency"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/en/articles/goroutines-vs-event-loop"
      }
    },
    {
      "id": "d29966ce002119b0",
      "url": "https://imrafaeldev.site/experiencias/sustentec",
      "title": "Sustentec",
      "content": "- [Início](/)\n- Sustentec\n\n## Sustentec\n\nMinha experiência na Sustentec com sistemas de pesquisa, APIs e qualidade.\n\nCargo\n\nEngenheiro de Software Full Stack Pleno\n\nPeríodo\n\njan/2021 a ago/2021\n\n## O contexto\n\nNa Sustentec, trabalhei em sistemas ligados a laboratórios, pesquisa e desenvolvimento. A experiência combinou a manutenção de um produto existente com a evolução de funcionalidades e integrações.\n\n## Como atuei\n\nDesenvolvi uma API REST em Dart com Shelf para integrar bases de instituições de pesquisa. Também mantive e evoluí um sistema de gestão de laboratórios com Java, Spring Boot, JPA, Hibernate, PostgreSQL e Angular.\n\nImplementei testes de integração onde antes não havia essa cobertura, desenvolvi relatórios e entregas ponta a ponta e participei da coleta de requisitos com clientes e do refinamento de sprints com o Product Owner.\n\n## O que levo\n\nEssa experiência reforçou que qualidade não se limita a testes unitários. Em sistemas com várias camadas, eu precisava validar o comportamento real entre API, persistência e interface.",
      "description": "Minha experiência na Sustentec com sistemas de pesquisa, APIs e qualidade.",
      "keywords": [
        "sustentec",
        "experiência",
        "sistemas",
        "pesquisa",
        "2021",
        "qualidade",
        "laboratórios",
        "desenvolvi",
        "testes",
        "não"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/sustentec"
      }
    },
    {
      "id": "d2c4d21a1be825d9",
      "url": "https://imrafaeldev.site/curriculo",
      "title": "Currículo (Part 1)",
      "content": "- [Início](/)\n- Currículo\n\n## Engenharia para quando sistemas deixam de ser simples\n\nEngenheiro Backend Sênior\n\nEngenheiro Backend Sênior com mais de sete anos de experiência em sistemas distribuídos, plataformas transacionais e modernização de legados.\n\nAtuo com execução hands-on, arquitetura, liderança técnica, mentoria e colaboração com produto e stakeholders. Meu eixo principal é Node.js, Go e Java; React e Angular entram como atuação complementar em produtos que exigem continuidade entre backend e frontend.\n\n## Experiência\n\n- fev/2025 a mai/2026 Remoto Infosistemas Engenheiro de Software Sênior / Arquiteto de Software Expandir experiência Recolher experiência\nLiderei integrações em NestJS e Go e redesenhei fluxos entre microsserviços, reduzindo em cerca de 98% as falhas intermitentes nos fluxos críticos.\n\nContexto\n\nAtuei em plataformas de gestão para locadoras, frotas e montadoras, no time de arquitetura e em colaboração com especialistas de operação e dados.\n\nContribuição\n\nLiderei integrações em NestJS e Go e redesenhei fluxos entre microsserviços, tornando o comportamento diante de falhas mais previsível nos fluxos críticos.\n\n[Ler experiência completa](/experiencias/infosistemas/)\n\n- jul/2025 a dez/2025 Remoto EDS (Polícia Civil do Rio de Janeiro) Engenheiro Backend — Consultoria Expandir experiência Recolher experiência\nEstruturei um sistema de gestão de saúde com NestJS para uma operação pública crítica e evoluí rotas backend com foco em segurança, acesso e rastreabilidade.\n\nContexto\n\nA consultoria envolveu sistemas públicos sensíveis, com um sistema de gestão de saúde de alto volume e um ERP jurídico em evolução.\n\nContribuição\n\nEstruturei backend com NestJS, refatorei rotas legadas e reforcei segurança, controle de acesso e rastreabilidade de fluxos sensíveis.\n\n[Ler experiência completa](/experiencias/eds-policia-civil-rio/)",
      "description": "Currículo online de Rafael Pereira, engenheiro backend sênior com experiência em Node.js, Go e Java, e atuação complementar em React e Angular.",
      "keywords": [
        "experiência",
        "para",
        "nestjs",
        "engenheiro",
        "node",
        "backend",
        "remoto",
        "expandir",
        "recolher",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/curriculo"
      }
    },
    {
      "id": "d3fb380eec01ac8e",
      "url": "https://imrafaeldev.site/en/projects/post-engine",
      "title": "Post Engine",
      "content": "- [Home](/en/)\n- [Projects](/en/projects/)\n- Post Engine\n\nEditorial workstation\n\n## Post Engine\n\nThe adaptive interview extracts evidence; the hybrid gateway (LLM + heuristics) blocks invented lived experience. Only confirmed content moves to draft and Markdown or SlideMark export.\n\n[Repository](https://github.com/imrafaeldev/post-engine)\n\nAutoria antes da geração\nEntrevista extrai evidência; briefing e storyboard preparam o material; o gateway veta fabricado. Só o confirmado segue para rascunho e export.\n\n- [01Problem](#problem)\n- [02Constraints](#constraints)\n- [03Decision](#decision)\n- [04Current state](#current-state)\n- [05Limitations](#limitations)\n\n## Problem\n\nGenerating a “professional” post with an LLM from a slogan invents biography. The flow must interview, identify gaps, prepare the briefing, draft, and export — and refuse what was not confirmed.\n\n## Constraints\n\nPython core with boundaries between interview, generation, authorship preservation, segmentation, and persistence. Model calls in an isolated workspace, provider allowlist, and prompts as versioned contracts. Hybrid evaluation: LLM plus deterministic heuristics. Missing experience never becomes false lived experience.\n\n## Decision\n\nAdaptive interviews, authorial briefing, storyboard, veto on fabricated content, SQLite prompt registry with rollback, textual interface, and React/Vite frontend to review phases. Markdown or SlideMark JSON export after evaluation.\n\n## Current state\n\nBase with tests for interview, LLM isolation, registry, persistence, and SlideMark conversion.\n\n## Limitations\n\nIt is not a generic thought-leadership generator. Without real repertoire, the system refuses; it does not complete the biography.",
      "description": "Authorship-centered editorial workstation: interview, briefing, storyboard, and export after the gateway blocks fabricated content. Versioned prompts; isolated LLM workspace.",
      "keywords": [
        "interview",
        "export",
        "with",
        "post",
        "experience",
        "slidemark",
        "briefing",
        "projects",
        "engine",
        "adaptive"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/projects/post-engine"
      }
    },
    {
      "id": "d42295eae6ea312d",
      "url": "https://imrafaeldev.site/experiences/maxmilhas-pt-br",
      "title": "Maxmilhas | Rafael Pereira",
      "content": "## O contexto\n\nNa Maxmilhas, trabalhei em um período curto e intenso, em fluxos de pós-venda ligados a cancelamentos, remarcações, cupons e comunicação com clientes.\n\n## Como atuei\n\nDesenvolvi microsserviços em Node.js, NestJS e Elixir para automatizar processos que ainda dependiam do suporte. Implementei regras de elegibilidade, expiração e cumulatividade de cupons, além de cálculos e validações necessários aos fluxos de cancelamento e remarcação. O conjunto dessas automações reduziu em 34% a necessidade de intervenção manual.\n\nOutro desafio foi integrar um monólito PHP 5.7, com mais de dez anos, ao CRM comercial sem colocar em risco o core da operação. Também evoluí monitoramento de voos, notificações e mensagens proativas.\n\n## O que levo\n\nAprendi a priorizar intervenções pequenas e reversíveis quando o resultado precisava aparecer rápido. Em vez de propor uma transformação ampla, concentrei a mudança nos pontos que liberavam trabalho operacional.",
      "description": "Minha experiência na Maxmilhas com automação de pós-venda e legado.",
      "keywords": [
        "fluxos",
        "cupons",
        "contexto",
        "maxmilhas",
        "trabalhei",
        "período",
        "curto",
        "intenso",
        "pós-venda",
        "ligados"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-maxmilhas",
        "slug": "maxmilhas",
        "title": "Maxmilhas | Rafael Pereira",
        "description": "Minha experiência na Maxmilhas com automação de pós-venda e legado.",
        "company": "Maxmilhas",
        "role": "Engenheiro de Software Full Stack",
        "period": "abr/2023 a out/2023",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/maxmilhas-pt-br.md"
      }
    },
    {
      "id": "d4618d111ba2533d",
      "url": "https://imrafaeldev.site/cases/infosistemas-mensageria-en",
      "title": "Infosistemas: a failure contract for RabbitMQ messaging (Part 1)",
      "content": "## Context\n\nInfosistemas operates management platforms for rental companies, fleets, and automakers. The work happened in the architecture team, in collaboration with DevOps, SREs, and DBAs, between February 2025 and May 2026.\n\nThis case covers the messaging track. Other tracks from the same experience (ERP security, signing journeys, fiscal integrations) exist in the sources but are not included here as extra numbers or claims.\n\n## Constraints\n\nCritical flows crossed microservices. Failure was intermittent: the same operation could complete on one run and fail on the next. Raising concurrency or prefetch without criteria transferred overload to consumers, services, or downstream databases.\n\n## Problem\n\nMessages stopped completing the expected flow. Investigating partial failure was hard. There was no explicit contract for temporary failure, permanent failure, duplication, or poison messages.\n\n## Decision\n\nMessaging was redesigned to make failure behavior predictable:\n\n- durable queues;\n- per-flow DLQ, so a message that must neither disappear nor repeat without control has a place to go;\n- retry with backoff for temporary unavailability;\n- idempotency and deduplication in the consumer, because duplicated delivery must not repeat a business effect;\n- publisher confirms, to reduce uncertainty at publish time;\n- prefetch tuning, instead of opening concurrency indiscriminately.\n\n## Discarded alternative\n\nTreating the problem as a capacity shortage (more consumers, more prefetch) without changing the failure contract. That would move the bottleneck and keep silent loss or duplication.\n\n## Implementation\n\nThe redesign applied these mechanisms to the critical flows: confirmed publishing, durable queues with controlled prefetch, idempotent consumers, retry with backoff, and per-flow DLQ. Operations gained a predictable path for temporary failure and for permanent failure, instead of relying on ad hoc reprocessing.\n\n## Result",
      "description": "RabbitMQ messaging redesign at Infosistemas with durable queues, DLQ, retry, idempotency, and prefetch. The work reduced intermittent failures between microservices by about 98%.",
      "keywords": [
        "failure",
        "with",
        "prefetch",
        "flows",
        "that",
        "other",
        "tracks",
        "critical",
        "without",
        "consumers"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "infosistemas-mensageria",
        "slug": "rabbitmq-messaging",
        "title": "Infosistemas: a failure contract for RabbitMQ messaging",
        "description": "RabbitMQ messaging redesign at Infosistemas with durable queues, DLQ, retry, idempotency, and prefetch. The work reduced intermittent failures between microservices by about 98%.",
        "company": "Infosistemas",
        "role": "Senior Software Engineer / Software Architect",
        "period": "Feb/2025 – May/2026",
        "excerpt": "Intermittent failures between microservices with no contract for retry, DLQ, or duplication. Adding more consumers only pushed the overload elsewhere.",
        "proofValue": "~98%",
        "proofLabel": "fewer intermittent failures in critical flows",
        "featuredClaimId": "infosistemas-rabbitmq-reliability",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/infosistemas-mensageria-en.md"
      }
    },
    {
      "id": "d47165bf1fd95582",
      "url": "https://imrafaeldev.site/projetos/post-engine",
      "title": "Post Engine",
      "content": "- [Início](/)\n- [Projetos](/projetos/)\n- Post Engine\n\nEstação de trabalho editorial\n\n## Post Engine\n\nA entrevista adaptativa extrai evidências; o gateway híbrido (LLM + heurística) barra vivência inventada. Apenas conteúdo confirmado segue para rascunho e exportação em Markdown ou SlideMark.\n\n[Repositório](https://github.com/imrafaeldev/post-engine)\n\nAutoria antes da geração\nEntrevista extrai evidência; briefing e storyboard preparam o material; o gateway veta fabricado. Só o confirmado segue para rascunho e export.\n\n- [01Problema](#problema)\n- [02Restrições](#restricoes)\n- [03Decisão](#decisao)\n- [04Estado atual](#estado-atual)\n- [05Limitações](#limitacoes)\n\n## Problema\n\nGerar post “profissional” com LLM a partir de um slogan inventa biografia. O fluxo precisa entrevistar, identificar lacunas, preparar o briefing, redigir e exportar, e recusar o que não foi confirmado.\n\n## Restrições\n\nNúcleo em Python com fronteiras entre entrevista, geração, preservação de autoria, segmentação e persistência. Chamadas a modelo em workspace isolado, allowlist de provedor e prompts como contratos versionados. Avaliação híbrida: LLM mais heurísticas determinísticas. Ausência de experiência nunca vira falsa vivência.\n\n## Decisão\n\nEntrevistas adaptativas, briefing autoral, storyboard, veto a conteúdo fabricado, registry SQLite de prompts com rollback, interface textual e frontend React/Vite para revisar fases. Exportação Markdown ou SlideMark JSON após avaliação.\n\n## Estado atual\n\nBase com testes de entrevista, isolamento de LLM, registry, persistência e conversão SlideMark.\n\n## Limitações\n\nNão é um gerador genérico de thought leadership. Sem repertório real, o sistema recusa; não completa a biografia.",
      "description": "Workstation editorial centrada em autoria: entrevista, briefing, storyboard e exportação após o gateway barrar conteúdo fabricado. Prompts versionados; workspace LLM isolado.",
      "keywords": [
        "entrevista",
        "post",
        "confirmado",
        "para",
        "slidemark",
        "briefing",
        "não",
        "projetos",
        "engine",
        "extrai"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/projetos/post-engine"
      }
    },
    {
      "id": "d4989ef745397b3e",
      "url": "https://imrafaeldev.site/articles/go-intensivo-es",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 5)",
      "content": "G es goroutine, M es hilo del sistema y P es recurso lógico de ejecución. `GOMAXPROCS` limita cuántos Ps ejecutan código Go en paralelo, no cuántas goroutines pueden existir. Las goroutines son ligeras, no gratuitas. Mide antes de usar pools u optimizar microdetalles. Preasigna capacidad conocida para slices, evita conversiones repetidas entre `string` y `[]byte` en hot paths y trata `sync.Pool` como cache oportunista de temporales.\n\n## Respuestas para entrevista\n\n**¿Una goroutine es un hilo?** No. Es una unidad ligera gestionada por el runtime y multiplexada sobre hilos del sistema operativo.\n\n**¿Channel o mutex?** Channel para transferir trabajo u ownership; mutex para proteger estado compartido e invariantes.\n\n**¿Quién cierra un channel?** El productor que sabe que no habrá más envíos.\n\n**¿Cómo preservar orden con workers?** Evita orden global si no es requisito. Particiona por una clave como `device_id` y procesa cada partición secuencialmente.\n\n**¿Cómo manejas duplicatas?** Con una clave de idempotencia estable, deduplicación transaccional cuando sea posible y operaciones idempotentes, como upsert con versión o secuencia.\n\n**¿Cómo investigas latencia?** Separa tiempo de fila, procesamiento y dependencias. Compara p95/p99, lag y saturación; después prueba una hipótesis con tracing, `pprof` o `go tool trace`.\n\n## Checklist de producción\n\n- ¿Cada goroutine tiene owner y condición de término?\n- ¿Dónde se propagan cancelación y deadline del contexto?\n- ¿Cuál es el límite de concurrencia y qué pasa en saturación?\n- ¿El orden necesario es por clave o global?\n- ¿Cuándo ocurre ACK y cómo la mutación resiste reentrega?\n- ¿Qué fallos hacen retry, van a DLQ o a cuarentena?\n- ¿Cómo se diferencian tiempo del dispositivo y tiempo de ingesta?\n- ¿Qué métricas exponen lag, p99, heap, GC y contención?\n\n## Referencias oficiales",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "orden",
        "channel",
        "context",
        "https",
        "return",
        "cada",
        "más",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-go-intensive",
        "slug": "go-intensivo",
        "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos",
        "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
        "excerpt": "Una guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 4,
        "totalChunks": 6,
        "sourcePath": "articles/go-intensivo-es.md"
      }
    },
    {
      "id": "d6bbdac27c799696",
      "url": "https://imrafaeldev.site/articles/design-patterns-strategy-es",
      "title": "Design Patterns: Strategy (Part 1)",
      "content": "Las cadenas de `if/else` crecen y se vuelven frágiles. El patrón Strategy encapsula cada algoritmo en su propia clase y permite intercambiar implementaciones en tiempo de ejecución sin alterar el código que las consume.\n\n## El problema: if/else infinito\n\nUna calculadora con suma, resta, multiplicación y división suele nacer como una clase `DefaultCalculator`: métodos privados por operación y una función pública que elige cuál invocar con `switch` o cadena de `if/else`.\n\n```typescript\nclass DefaultCalculator {\n  public calculate(parameters: BinaryOperationParameters): Result {\n    const { operator, firstOperand, secondOperand } = parameters;\n\n    switch (operator) {\n      case \"*\":\n        return firstOperand * secondOperand;\n      case \"+\":\n        return firstOperand + secondOperand;\n      case \"-\":\n        return firstOperand - secondOperand;\n      case \"/\":\n        return firstOperand / secondOperand;\n      case \"**\":\n        return firstOperand ** secondOperand;\n      case \"%\":\n        return firstOperand % secondOperand;\n      default:\n        throw new Error(\"Operator not found!\");\n    }\n  }\n}\n```\n\nEl problema aparece cuando la calculadora necesita cubrir más operaciones binarias entre enteros: porcentaje, exponenciación, módulo, shift de bits. Cada funcionalidad nueva altera la implementación original, sube el acoplamiento y encarece el mantenimiento.\n\n## ¿Qué es el patrón Strategy?\n\nEl patrón define la funcionalidad por medio de un contrato (interfaz), implementado según el contexto. La interfaz define la operación; las implementaciones concretas definen su ejecución.\n\nEl código consumidor depende de la abstracción. Cada estrategia queda aislada en su propia clase. Nuevos comportamientos entran sin alterar el código existente, alineado al Open/Closed Principle.\n\n## Definiendo el contrato\n\nEl primer paso es la interfaz del contrato de la estrategia. En la calculadora, algo que reciba dos números y devuelva el resultado:",
      "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "parameters",
        "operator",
        "strategy",
        "cada",
        "operación",
        "calculate"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-design-patterns-strategy",
        "slug": "design-patterns-strategy",
        "title": "Design Patterns: Strategy",
        "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
        "excerpt": "Las cadenas de if/else crecen y se vuelven frágiles. Strategy aísla cada algoritmo tras un contrato.",
        "publishedAt": "2022-05-05",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-strategy-es.md"
      }
    },
    {
      "id": "d7267100958ba02a",
      "url": "https://imrafaeldev.site/es/articulos/typescript-clean-architecture",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- TypeScript Clean Architecture: Core, Adapters e Infra\n\n## TypeScript Clean Architecture: Core, Adapters e Infra\n\nArquitectura débil traba mantenimiento, pruebas y cambio. Esta derivación de Clean Architecture para backend TypeScript separa Core, Adapters e Infra — con dependencias apuntando hacia dentro.\n\n15 de marzo de 2023\n\n- [Arquitectura](/es/articulos/?topic=arquitetura)\n\nEl desarrollo de software cambia todo el tiempo. La arquitectura débil se vuelve mantenimiento caro, feature lenta, prueba difícil y bug difícil de aislar. Vale invertir en una estructura que soporte evolución sin reescribir el sistema ante cada presión del negocio.\n\n## Un poco de historia\n\nClean Architecture es el nombre que Robert C. Martin (Uncle Bob) dio, en 2012, en el libro *Clean Architecture: A Craftsman’s Guide to Software Structure and Design*. La propuesta huye de la rigidez de arquitecturas acopladas a framework y base: el núcleo queda estable; los detalles externos cambian. La idea bebe de DDD, SOLID, Onion Architecture y Hexagonal Architecture.\n\n## Propuesta general\n\nEste artículo describe Clean Architecture y una derivación práctica para backends en TypeScript: tres capas — **Core**, **Adapters** e **Infra**.\n\n- **Core** — regla de negocio y entidades del dominio. Capa más interna.\n\n- **Infra** — conexiones externas: repositorios concretos, controllers REST, módulos de DI, boilerplate de framework.\n\n- **Adapters** — intermediación en ambos sentidos. El controller no llama al usecase “crudo”: pasa por un servicio. El usecase no habla con la base: habla con un protocolo que un adapter (repositorio, connector, handler) implementa.\n\nCada capa tiene capacidades y restricciones distintas; SOLID pesa más en el Core. Sirve para CRUD HTTP y para sistemas con varios frameworks y canales.",
      "description": "Derivación de Clean Architecture para backend TypeScript: Core con usecases y protocols, Adapters bidireccionales e Infra NestJS con inyección de dependencias.",
      "keywords": [
        "name",
        "string",
        "core",
        "userentity",
        "this",
        "regla",
        "export",
        "architecture",
        "para",
        "class"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/typescript-clean-architecture"
      }
    },
    {
      "id": "d7856130e4c27cfd",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-pt-br",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 6)",
      "content": "## Duas horas para localizar a camada, um dia para testar\n\n“Achamos o gargalo” costuma virar “resolvemos a lentidão” cedo demais. Depois de localizar uma consulta ruim ou um trecho sequencial, ainda faltam a mudança, o deploy e a confirmação em produção. Encontrar a camada apenas encurta a lista de suspeitos.\n\nNas primeiras duas horas, quero localizar onde aprofundar: banco, aplicação, integração ou infraestrutura. Abro o plano, comparo tempo e leituras, conto consultas e cronometro o trecho executado depois do banco.\n\nSe o plano está saudável e cinquenta chamadas de 40 ms somam cerca de dois segundos, paro de procurar índice e meço o código. Se as leituras explodem antes da agregação, o código espera enquanto testo a consulta.\n\nEsse prazo é uma régua de investigação, não uma garantia. No dashboard lento, passei cerca de três horas no terminal olhando o plano, comparando métricas e testando a quebra da consulta. O restante do dia envolveu reunião, acesso, deploy e confirmação em produção.\n\nPor isso, no primeiro dia quero ao menos uma hipótese testada com medida de antes e depois. O teste pode reescrever o filtro sem `CAST`, dividir a query ou limitar a concorrência do loop. Depois do deploy, comparo a métrica que motivou a mudança: leituras e CPU para o banco; latência, erros e saturação para a rota.\n\nEm falhas intermitentes, essa janela cresce. É preciso esperar o sintoma reaparecer com métricas suficientes para comparar.\n\n## A confirmação acontece depois do deploy\n\nDepois de alguns erros em produção, parei de procurar um culpado padrão. Banco, aplicação e infraestrutura entram como hipóteses, cada uma acompanhada de uma medida que pode confirmá-la ou descartá-la.\n\nQuando o plano mostra leituras altas e cardinalidade distante da realidade, trabalho na consulta. Quando a consulta responde bem e o profiling concentra tempo depois dela, sigo para o código ou para a integração.",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "depois",
        "antes",
        "não",
        "quando",
        "leituras"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-perto-dos-dados",
        "title": "A maioria dos problemas de performance de backend começa perto dos dados",
        "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
        "excerpt": "API lenta e a reunião já enche de soluções. Eu começo perto dos dados — não por culpar o banco, mas porque essa verificação costuma dar sinal rápido.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-pt-br.md"
      }
    },
    {
      "id": "d793e8d54d1aa861",
      "url": "https://imrafaeldev.site/articles/go-intensive-en",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 1)",
      "content": "This is a review for backend engineers who already know Go and need to discuss or build production services again. The useful decisions are bounded concurrency, cancellation, finite queues, idempotency, and observability.\n\nThe scope is Go 1.26. It is not a language introduction. Move quickly through syntax and spend time on the choices that shape a consumer, API, or telemetry pipeline.\n\n## 30-minute route\n\n| Time | Topic | Priority |\n| --- | --- | --- |\n| 0-4 min | Types, structs, interfaces, errors | Quick review |\n| 4-10 min | Goroutines, channels, `select`, context | High |\n| 10-17 min | Bounded concurrency and backpressure | Highest |\n| 17-23 min | Resilient IoT pipeline | Highest |\n| 23-26 min | Runtime, memory, profiling | High |\n| 26-30 min | Architecture and interview answers | Highest |\n\n## Production fundamentals\n\nGo favors composition, small contracts, and explicit flow. A value can validate itself without a framework:\n\n```go\nvar ErrOutOfRange = errors.New(\"reading out of range\")\n\ntype Reading struct {\n\tDeviceID string\n\tSequence uint64\n\tValue    float64\n}\n\nfunc (r Reading) Validate() error {\n\tif r.DeviceID == \"\" {\n\t\treturn errors.New(\"device_id is required\")\n\t}\n\tif r.Value < -100 || r.Value > 250 {\n\t\treturn fmt.Errorf(\"%w: %.2f\", ErrOutOfRange, r.Value)\n\t}\n\treturn nil\n}\n```\n\nThe zero value is often useful. Slices share a backing array until `append` reallocates; maps have no iteration order and need synchronization for concurrent access. Strings are immutable bytes, usually UTF-8. `defer` runs in LIFO order, while evaluating its arguments when registered.\n\nInterfaces are satisfied implicitly. Define a small interface where it is consumed. Errors are values: add context with `%w` and inspect the chain with `errors.Is` or `errors.As`. Reserve `panic` for broken invariants or unrecoverable startup failures.\n\n## Goroutines, channels, and context",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "context",
        "reading",
        "channel",
        "https",
        "errors",
        "device",
        "with",
        "time",
        "return",
        "goroutine"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-go-intensive",
        "slug": "go-intensive",
        "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes",
        "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
        "excerpt": "A 30-minute guide to reactivate production Go knowledge for backend services, telemetry ingestion, and distributed systems.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "articles/go-intensive-en.md"
      }
    },
    {
      "id": "d7bf08d7f922cd61",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-en",
      "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern (Part 2)",
      "content": "The service loses knowledge of how the CRUD reaches the database. It is composed of protocols; the concrete implementation arrives at runtime — via dependency injection. While we use PostgreSQL, we implement the protocols in adapters (or connectors).\n\n`CreateDatabaseCustomerProtocol` can be implemented by `CreateDatabaseCustomerPostgresqlAdapter`, `CreateDatabaseCustomerMysqlAdapter`, `CreateDatabaseCustomerMongoDBAdapter`, or `CreateDatabaseCustomerMockedAdapter`. The service looks like this:\n\n```typescript\nclass CustomerService {\n  constructor(\n    private readonly createCustomer: CreateDatabaseCustomerProtocol,\n  ) {}\n\n  public register(customer: CustomerInputEntity): SuccessfulEntityCreation {\n    return this.createCustomer.createCustomerOnDatabase(customer);\n  }\n}\n```\n\nFor the service, it does not matter whether the database returns JSON, XML, or another format — the adapter translates to the contract the business rule expects.\n\n## Advantages\n\n- **Maintenance:** any plugin can be replaced without rewriting the service.\n- **Tests:** to test only the business rule, inject a mock adapter implementing the same protocol.\n- **Clean code:** separated responsibilities; the business rule does not carry driver details.\n\n## Next step\n\nTake a frontend with dozens of libraries and identify what you actually use. Pick one feature — converting BRL to USD, for example. Describe the contract (input and output) and implement an adapter on top of the library that does it today. Repeat where the dependency hurts.\n\n## Relation to other patterns\n\nIn the article [Design Patterns: Strategy](/en/articles/design-patterns-strategy/), the focus is swapping algorithms behind a contract. The Adapter isolates external dependencies behind an owned interface. The two complement each other: Strategy varies behavior; Adapter translates the outside world.\n\n---",
      "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
      "keywords": [
        "adapter",
        "service",
        "database",
        "business",
        "rule",
        "that",
        "interface",
        "with",
        "readonly",
        "dependency"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Stop being hostage to dependencies. Say hello to the Adapter design pattern",
        "description": "With the Adapter, the service depends on a protocol and adapters translate MySQL, PostgreSQL, or mocks — without coupling business rules to the driver.",
        "excerpt": "Migrating from MySQL to PostgreSQL does not require rewriting the service. The Adapter isolates the plugin behind a contract the business rule understands.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-en.md"
      }
    },
    {
      "id": "d7bf1e86d41a596f",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-pt-br",
      "title": "Desenferrujando a lógica #01: Group Anagrams (Part 3)",
      "content": "```go\nfunc groupAnagrams(strs []string) [][]string {\n\tgroups := make(map[[26]uint8][]string, len(strs))\n\n\tfor _, str := range strs {\n\t\tvar key [26]uint8\n\n\t\tfor i := 0; i < len(str); i++ {\n\t\t\tkey[str[i]-'a']++\n\t\t}\n\n\t\tgroups[key] = append(groups[key], str)\n\t}\n\n\tresult := make([][]string, 0, len(groups))\n\n\tfor _, group := range groups {\n\t\tresult = append(result, group)\n\t}\n\n\treturn result\n}\n```\n\n## O que mudou em complexidade?\n\n- Ordenação: `O(k log k)` por string e `O(n × k log k)` para `n` strings.\n- Contagem: `O(k)` por string e `O(n × k)` para `n` strings.\n\nA melhoria apareceu quando percebi que a ordenação fazia um trabalho que o problema não exigia. A mudança principal é de `O(k log k)` para `O(k)` por string.\n\n## A primeira solução não estava errada\n\nEla resolve o problema e, dependendo do contexto, poderia ser suficiente. O hábito que eu queria recuperar era continuar pensando depois que o código começa a funcionar. Encontrar uma solução, voltar ao problema e perguntar: que trabalho meu algoritmo está fazendo sem precisar?\n\nNão estou fazendo esses exercícios porque LeetCode representa todo o trabalho de engenharia de software, nem para disputar a solução mais sofisticada. Estou fazendo porque percebi que usar IA todos os dias reduziu a quantidade de vezes em que preciso insistir sozinho em um problema. Quero reservar espaço para exercitar isso novamente: ler, tentar, errar, revisar a abordagem e só depois comparar caminhos.\n\n---\n\n*Série Desenferrujando a lógica #01 — Group Anagrams. Adaptado do carousel autoral; problema em [leetcode.com/problems/group-anagrams](https://leetcode.com/problems/group-anagrams/).*",
      "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "chave",
        "não",
        "solução",
        "result",
        "problema",
        "groups",
        "group"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "desenferrujando-logica-group-anagrams",
        "title": "Desenferrujando a lógica #01: Group Anagrams",
        "description": "LeetCode 49 em Go: da chave por sort à contagem de 26 letras, e o hábito de continuar pensando depois que o código funciona.",
        "excerpt": "Eu não tinha parado de escrever código. O que mudou foi terceirizar partes do raciocínio. Group Anagrams foi o exercício para recuperar o hábito.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-pt-br.md"
      }
    },
    {
      "id": "d8a74e0f0df0c9c0",
      "url": "https://imrafaeldev.site/cases/flapper-modernizacao-en",
      "title": "Flapper: discovering the domain before splitting the monolith (Part 1)",
      "content": "## Context\n\nAt Flapper, the core product was a PHP monolith over seven years old, with little useful documentation and without the developers who had created it. The application sustained the executive aviation operation and could not be stopped for a rewrite.\n\n## Constraints\n\nCode and database accumulated rules and dependencies that were hard to explain. Changes had unpredictable side effects, and there were no remaining specialists to confirm how each part of the system should evolve. Migration had to coexist with the product in production.\n\n## Problem\n\nSwitching PHP for another technology would not answer the main question: which rules belonged together and which dependencies could be separated without breaking operations. The system needed domain boundaries before new services.\n\n## Decision\n\nI used the existing database and code as a discovery source. Table groupings and relationships helped identify bounded contexts; from there, migration followed the Strangler pattern:\n\n1. extract one domain at a time, without stopping the monolith;\n2. keep in each context only the local representation of the data it needed;\n3. propagate changes through Kafka events, instead of connecting every service to the old database;\n4. use Node.js, NestJS, and Go in the first modules, with gRPC, REST, or GraphQL depending on the consumer;\n5. document the strategy and the first modules so the team could continue the transformation.\n\n## Discarded alternative\n\nRewriting the whole monolith, or keeping new services tied to the same database and shared relationships. The first option would stop the business; the second would preserve the coupling the migration needed to reduce.\n\n## Result\n\nThe domain-based split reduced the table count by approximately 25% and created seven databases organized by context. The people, authentication, and aircraft modules were the first steps of a transformation planned to continue beyond the initial delivery.\n\n## Limitations",
      "description": "Incremental modernization of an undocumented PHP monolith at Flapper: database as discovery source, bounded contexts, and Strangler. The domain-based split reduced the table count by approximately 25%.",
      "keywords": [
        "monolith",
        "database",
        "migration",
        "domain",
        "first",
        "context",
        "with",
        "without",
        "could",
        "rules"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "flapper-modernizacao",
        "slug": "undocumented-monolith-modernization",
        "title": "Flapper: discovering the domain before splitting the monolith",
        "description": "Incremental modernization of an undocumented PHP monolith at Flapper: database as discovery source, bounded contexts, and Strangler. The domain-based split reduced the table count by approximately 25%.",
        "company": "Flapper",
        "role": "Full Stack Software Engineer",
        "period": "Sep/2021 – Jun/2022",
        "excerpt": "The product could not stop, and the original authors were already gone. Before migrating, we had to discover which boundaries the database still revealed.",
        "proofValue": "~25%",
        "proofLabel": "fewer tables in the domain-based split",
        "featuredClaimId": "flapper-domain-modernization",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/flapper-modernizacao-en.md"
      }
    },
    {
      "id": "d9a671b494f6f921",
      "url": "https://imrafaeldev.site/projects/diffvision-es",
      "title": "DiffVision",
      "content": "## Problema\n\nRevisar un diff Git en herramienta SaaS envía código fuera y mezcla UI remota con el historial local. La revisión necesita funcionar offline, con hunks, filtros, bookmarks y comentarios anclados en líneas.\n\n## Restricciones\n\nPreferencias e informes deben vivir en el propio repositorio (`.diffvision/`). La CLI npm inicia backend y UI locales. La integración con asistente no puede venderse como lista si aún es prototipo.\n\n## Decisión\n\nLa CLI inspecciona el Git, interpreta diff unificado y sube interfaz web. Backend Fastify con snapshot y WebSocket; UI React/Vite. Exportación Markdown/JSON en el repositorio. Paquete `diffvision-mcp` por stdio para resumir el repositorio, leer patches y registrar comentarios. El asistente visual de revisión por IA se declara mock/prototipo; la escritura de comentarios vía MCP es funcional.\n\n## Estado actual\n\nDistribuido como CLI npm, con ejecución local-first.\n\n## Limitaciones\n\nNo sustituye el flujo de review de GitHub. El flujo de IA visual no debe leerse como producto acabado.",
      "description": "CLI npm local-first para revisar diffs Git, con UI local, comentarios en el repositorio, exportación en Markdown/JSON y servidor MCP. La revisión visual por IA permanece mock.",
      "keywords": [
        "comentarios",
        "repositorio",
        "como",
        "diff",
        "revisión",
        "backend",
        "asistente",
        "prototipo",
        "visual",
        "flujo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "projeto-diffvision",
        "slug": "diffvision",
        "title": "DiffVision",
        "description": "CLI npm local-first para revisar diffs Git, con UI local, comentarios en el repositorio, exportación en Markdown/JSON y servidor MCP. La revisión visual por IA permanece mock.",
        "excerpt": "El diff Git abre en la UI local; comentarios y exportación en Markdown quedan en el repositorio. La revisión visual por IA permanece mock.",
        "repoUrl": "https://github.com/imrafaeldev/diffvision-app",
        "status": "CLI pública; revisión por IA aún mock",
        "featured": "false",
        "order": "4",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "projects/diffvision-es.md"
      }
    },
    {
      "id": "da1f96ee223232e9",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-en",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 4)",
      "content": "But Go also charges a price. You must think about chunk granularity, memory consumption, cancellation, error handling, backpressure, worker limits, contention, and result consistency.\n\nIf the work split is naive, the CPU gain can arrive with memory blowups or needless complexity.\n\n## Counterargument: Node.js also has worker threads\n\nThere is a fair counterargument: Node.js is not limited to the Event Loop for everything. Worker threads exist precisely to run heavy work outside the main thread. There are also strategies with queues, separate processes, helper services, and native addons.\n\nSo the honest comparison is not \"Node.js cannot\". It can.\n\nThe question is implementation cost, team maturity, observability, integration with the existing system, and how much effort is worth investing to keep that processing inside the Node ecosystem.\n\nOn some teams, worker threads may be enough and cheaper than introducing Go. On others, splitting CPU-bound processing into a Go service may be simpler to operate and scale.\n\nThe decision should not come from language preference. It should come from the nature of the load.\n\n## The practical rule that stuck\n\nAfter that case, the rule that stuck for me is this: if the problem is waiting on many things at once, Node.js with the Event Loop tends to orchestrate very well. If the problem is computing many things at once, I consider Go with goroutines earlier.\n\nBut I also grew suspicious of migrations promising to fix everything. Switching technology may only move the bottleneck.\n\nIn my case, CPU and time improved, but memory and chunking had to be reanalyzed.\n\nIn the end, the goroutines vs Event Loop fight is less about which model is superior and more about which bottleneck you are trying to attack.\n\nFor I/O, the corner-store counter works very well. For CPU, sometimes you really need more people in the festival kitchen.\n\n---",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "work",
        "loop",
        "problem",
        "event",
        "time",
        "memory",
        "same"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models",
        "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
        "excerpt": "Node.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-en.md"
      }
    },
    {
      "id": "da60659ad0f90cab",
      "url": "https://imrafaeldev.site/articles/derusting-logic-container-with-most-water-en",
      "title": "Derusting logic #02: Container With Most Water (Part 1)",
      "content": "After solving the first exercise of the series, I stayed on LeetCode to work on logic without asking for a ready-made solution. Challenge 011 is [Container With Most Water](https://leetcode.com/problems/container-with-most-water/).\n\nWe get an array of heights and need to pick two lines that form the container with the largest area.\n\n## How to compute the area\n\nIf I pick positions `left` and `right`, the width is the distance between them. The container height is limited by the shorter of the two lines.\n\n```text\narea = min(left height, right height) × distance\n```\n\nFor example, with these heights:\n\n```text\n[1, 8, 6, 2, 5, 4, 8, 3, 7]\n```\n\nThe lines at positions `1` and `8` have heights `8` and `7`. The shorter height is `7`, and the distance between them is `7`. That combination produces an area of `49`.\n\n## My first attempt\n\nI started with two pointers, one at each end of the array. After computing the current area, I simulated two possibilities:\n\n- advancing the left pointer;\n- moving the right pointer back.\n\nI computed the area of the next two pairs and picked the larger one. The main excerpt was this:\n\n```javascript\nconst paddingLeftArea =\n  Math.min(heights[leftIndex + 1], heights[rigthIndex]) *\n  (rigthIndex - leftIndex + 1);\n\nconst paddingRightArea =\n  Math.min(heights[leftIndex], heights[rigthIndex - 1]) *\n  (rigthIndex - 1 - leftIndex);\n\nif (paddingLeftArea > paddingRightArea && paddingLeftArea > maxArea) {\n  leftIndex += 1;\n} else {\n  rigthIndex -= 1;\n}\n```\n\nThe problem was in the hypothesis. The best local decision does not guarantee the best area over the rest of the array. I tried to guess the path by looking only at the next two moves. There was also an error in the distance formula of that draft: for a pair of positions, the width is `right - left`.\n\n## The observation that unlocks the problem\n\nThe area depends on two things: width and shorter height.",
      "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
      "keywords": [
        "heights",
        "area",
        "leftindex",
        "rigthindex",
        "that",
        "height",
        "maxarea",
        "left",
        "pointers",
        "javascript"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "derusting-logic-container-with-most-water",
        "title": "Derusting logic #02: Container With Most Water",
        "description": "LeetCode 11 in JavaScript: a neighbor-based attempt, the hypothesis review, and the two-pointer solution.",
        "excerpt": "I tried to pick the next step by looking only at the neighbors. It worked in some cases, but the problem asked for a wider view.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/derusting-logic-container-with-most-water-en.md"
      }
    },
    {
      "id": "dbbeadc1e6214a2d",
      "url": "https://imrafaeldev.site/en/cases/rabbitmq-messaging",
      "title": "Infosistemas: a failure contract for RabbitMQ messaging (Part 2)",
      "content": "- retry with backoff for temporary unavailability;\n\n- idempotency and deduplication in the consumer, because duplicated delivery must not repeat a business effect;\n\n- publisher confirms, to reduce uncertainty at publish time;\n\n- prefetch tuning, instead of opening concurrency indiscriminately.\n\n## Discarded alternative\n\nTreating the problem as a capacity shortage (more consumers, more prefetch) without changing the failure contract. That would move the bottleneck and keep silent loss or duplication.\n\n## Implementation\n\nThe redesign applied these mechanisms to the critical flows: confirmed publishing, durable queues with controlled prefetch, idempotent consumers, retry with backoff, and per-flow DLQ. Operations gained a predictable path for temporary failure and for permanent failure, instead of relying on ad hoc reprocessing.\n\n## Result\n\nThe recorded reduction in intermittent failures in critical flows between microservices was approximately 98%. The number describes those flows after the redesign, not the entire company operation nor other tracks.\n\n## Limitations\n\nMetrics from other tracks that are still pending method or confirmation are left out. If volume or the microservice map changes so that DLQ and prefetch no longer isolate failure, tuning must be revisited with queue and consumer telemetry.\n\nContact\n\n## Dealing with a system that's stopped being simple?\n\nReach out directly via @imrafaeldev, no form — for professional conversation, start on LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Open the contact page](/en/contact/)",
      "description": "RabbitMQ messaging redesign at Infosistemas with durable queues, DLQ, retry, idempotency, and prefetch. The work reduced intermittent failures between microservices by about 98%.",
      "keywords": [
        "failure",
        "with",
        "prefetch",
        "contract",
        "flows",
        "imrafaeldev",
        "infosistemas",
        "messaging",
        "intermittent",
        "retry"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/cases/rabbitmq-messaging"
      }
    },
    {
      "id": "dd8f0e29c5214b26",
      "url": "https://imrafaeldev.site/es/contacto",
      "title": "Contacto",
      "content": "- [Inicio](/es/)\n- Contacto\n\nContacto\n\n## ¿Tienes un sistema que dejó de ser simple?\n\nEscríbeme directo por @imrafaeldev, sin formulario. Para conversación profesional, empieza en LinkedIn; para ver decisiones en código, ve a GitHub.\n\n- [Instagram @imrafaeldev](https://www.instagram.com/imrafaeldev/)\n- [YouTube @imrafaeldev](https://www.youtube.com/@imrafaeldev)\n- [GitHub @imrafaeldev](https://github.com/imrafaeldev)\n- [LinkedIn @imrafaeldev](https://www.linkedin.com/in/imrafaeldev/)",
      "description": "Habla con Rafael Pereira vía @imrafaeldev en LinkedIn, GitHub, Instagram y YouTube. Sin formulario: elige el canal y escribe directamente.",
      "keywords": [
        "imrafaeldev",
        "https",
        "linkedin",
        "github",
        "contacto",
        "para",
        "instagram",
        "youtube",
        "inicio",
        "tienes"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/contacto"
      }
    },
    {
      "id": "dee16169e982b7ef",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 4)",
      "content": "**Falha ou limite que ele trata.** Channels evitam condição de corrida por construção quando o dado atravessa o canal em vez de ser compartilhado. O limite: modelar todo estado compartilhado com uma goroutine \"dona\" e canais de pedido/resposta adiciona latência, complexidade e risco de deadlock quando um simples mutex resolveria. Inversamente, proteger um pipeline inteiro com um único mutex gigante serializa trabalho que poderia fluir em paralelo.\n\n**Exemplo de aplicação.** Cenário didático: o mesmo contador implementado das duas formas para comparar.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\n// Com mutex: direto para estado compartilhado simples.\ntype Counter struct {\n\tmu sync.Mutex\n\tn  int\n}\n\nfunc (c *Counter) Inc() {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tc.n++\n}\n\n// Com channel: a goroutine dona centraliza as atualizações.\nfunc runCounterOwner(increments int) int {\n\tinc := make(chan struct{})\n\tdone := make(chan int)\n\n\tgo func() {\n\t\ttotal := 0\n\t\tfor range inc {\n\t\t\ttotal++\n\t\t}\n\t\tdone <- total\n\t}()\n\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < increments; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tinc <- struct{}{}\n\t\t}()\n\t}\n\twg.Wait()\n\tclose(inc)\n\treturn <-done\n}\n\nfunc main() {\n\tvar c Counter\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < 100; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tc.Inc()\n\t\t}()\n\t}\n\twg.Wait()\n\tfmt.Println(\"mutex:\", c.n)\n\tfmt.Println(\"owner:\", runCounterOwner(100))\n}\n```\n\n**Quando escolher outra abordagem.** Prefira `sync.Mutex`/`sync.RWMutex` para proteger mapas, contadores e caches com acesso concorrente simples; prefira `sync.Map` apenas quando houver padrão comprovado de muitas leituras e poucas escritas com chaves disjuntas. Prefira channels quando precisar de fila, fan-out/fan-in, timeout via `select` ou backpressure natural com canal com buffer. Evite expor canais internos como API de uma estrutura com estado pequeno — o mutex mantém a interface síncrona e mais fácil de testar.\n\n## 4. Concorrência limitada e backpressure",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "e15c5a2a90c2a485",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-en",
      "title": "Derusting logic #01: Group Anagrams (Part 1)",
      "content": "After years programming, I realized my logic was rusty. I had not stopped writing code. What changed was that, little by little, I started outsourcing parts of the reasoning I used to exercise alone.\n\nI picked exercise [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/) on LeetCode. The task is to receive a list of strings and group anagrams together.\n\n## What we need to solve\n\nInput:\n\n```text\n[\"eat\", \"tea\", \"tan\", \"ate\", \"nat\", \"bat\"]\n```\n\nPossible result:\n\n```text\n[\"eat\", \"tea\", \"ate\"]\n[\"tan\", \"nat\"]\n[\"bat\"]\n```\n\nGroup order does not matter.\n\n## What is an anagram?\n\nTake `eat`, `tea`, and `ate`. Each has `a` once, `e` once, and `t` once. Positions change; the count of each letter stays the same.\n\nThe algorithm must turn those words into a shared representation. If all three produce the same key, I can use that key in a `map` and place them in the same group. The first problem is creating that key.\n\n## My first answer was sorting\n\nI started using the sorted string as the key:\n\n```text\neat → aet\ntea → aet\nate → aet\n```\n\nAll three produce `aet`.\n\n### Sort-based solution\n\nThat was the first solution that came to mind. I was not trying for the leanest implementation right away. I wanted a coherent solution and to understand where it could improve.\n\n```go\nfunc sortString(str string) string {\n\tb := []byte(str)\n\tslices.Sort(b)\n\treturn string(b)\n}\n\nfunc groupAnagrams(strs []string) [][]string {\n\tmapping := make(map[string][]string)\n\n\tfor _, str := range strs {\n\t\tsortedStr := sortString(str)\n\t\tmapping[sortedStr] = append(mapping[sortedStr], str)\n\t}\n\n\tresult := make([][]string, 0, len(mapping))\n\n\tfor _, group := range mapping {\n\t\tresult = append(result, group)\n\t}\n\n\treturn result\n}\n```\n\nWhat happens in this code:",
      "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
      "keywords": [
        "string",
        "group",
        "that",
        "result",
        "same",
        "solution",
        "each",
        "problem",
        "groups",
        "what"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "derusting-logic-group-anagrams",
        "title": "Derusting logic #01: Group Anagrams",
        "description": "LeetCode 49 in Go: from sort-based keys to 26-letter counting, and the habit of keeping thinking after the code works.",
        "excerpt": "I had not stopped writing code. What changed was outsourcing parts of the reasoning. Group Anagrams was the exercise to recover the habit.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-en.md"
      }
    },
    {
      "id": "e1764966256658a5",
      "url": "https://imrafaeldev.site/experiencias/eds-policia-civil-rio",
      "title": "EDS e Polícia Civil do Rio de Janeiro",
      "content": "- [Início](/)\n- EDS (Polícia Civil do Rio de Janeiro)\n\n## EDS (Polícia Civil do Rio de Janeiro)\n\nMinha experiência de consultoria em sistemas públicos sensíveis.\n\nCargo\n\nEngenheiro Backend — Consultoria\n\nPeríodo\n\njul/2025 a dez/2025\n\n## O contexto\n\nNa consultoria para a EDS, trabalhei em sistemas destinados à Polícia Civil do Rio de Janeiro. O contexto envolvia uma operação pública crítica, um sistema de gestão de saúde de alto volume e a evolução de um ERP jurídico.\n\n## Como atuei\n\nEstruturei o backend do sistema de saúde com NestJS e SQL Server. Também refatorei rotas legadas e participei do desenho de fluxos para automação de processos, gestão documental e coleta de evidências. Segurança, controle de acesso, rastreabilidade e LGPD não eram requisitos isolados: orientavam como cada rota precisava evoluir.\n\nAlém do backend, colaborei com a manutenção de componentes compartilhados do design system para alinhar contratos de API e o comportamento das interfaces usadas na operação.\n\n## O que levo\n\nFoi uma experiência que reforçou o cuidado necessário para evoluir sistemas sensíveis sem perder auditabilidade. Em vez de separar segurança da entrega, tratei acesso e rastreabilidade como parte do contrato do produto.",
      "description": "Minha experiência de consultoria em sistemas públicos sensíveis.",
      "keywords": [
        "para",
        "polícia",
        "civil",
        "janeiro",
        "consultoria",
        "sistemas",
        "backend",
        "como",
        "2025",
        "experiência"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/eds-policia-civil-rio"
      }
    },
    {
      "id": "e2920c4f3b3de5bd",
      "url": "https://imrafaeldev.site/es/proyectos/sms-manager",
      "title": "SMS Manager (Part 2)",
      "content": "Es un artefacto de estudio y operación local de mensajería, no un producto comercial con SLA de operadora. Los tres consumidores demuestran el contrato; no afirman que los tres lenguajes corran juntos en producción del autor.",
      "description": "Campañas de SMS desacopladas: la API Nest persiste y publica, la cola entrega y consumidores en TypeScript, Go o Rust graban el resultado. Token opaco, Redis y gRPC entre servicios.",
      "keywords": [
        "para",
        "consumidores",
        "operadora",
        "autenticación",
        "contrato",
        "repositorio",
        "rabbitmq",
        "usuarios",
        "empresas",
        "entre"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/es/proyectos/sms-manager"
      }
    },
    {
      "id": "e33a841dfd67beea",
      "url": "https://imrafaeldev.site/cases/flapper-modernizacao-es",
      "title": "Flapper: descubrir el dominio antes de separar el monolito (Part 1)",
      "content": "## Contexto\n\nEn Flapper, el producto principal era un monolito PHP con más de siete años, poca documentación útil y sin los desarrolladores que lo habían creado. La aplicación sostenía la operación de aviación ejecutiva y no podía interrumpirse para una reescritura.\n\n## Restricciones\n\nEl código y la base acumulaban reglas y dependencias difíciles de explicar. Los cambios tenían efectos colaterales poco predecibles, y no había especialistas remanentes para confirmar cómo cada parte del sistema debía evolucionar. La migración necesitaba coexistir con el producto en producción.\n\n## Problema\n\nCambiar PHP por otra tecnología no respondería la duda principal: qué reglas pertenecían juntas y qué dependencias podían separarse sin romper la operación. El sistema necesitaba fronteras de dominio antes de servicios nuevos.\n\n## Decisión\n\nUsé la base y el código existente como fuente de descubrimiento. Agrupamientos de tablas y relaciones ayudaron a identificar bounded contexts; a partir de ellos, la migración siguió el patrón Strangler:\n\n1. extraer un dominio por vez, sin interrumpir el monolito;\n2. mantener en cada contexto solo la representación local de los datos que necesitaba;\n3. propagar cambios por eventos en Kafka, en lugar de conectar todos los servicios a la base antigua;\n4. usar Node.js, NestJS y Go en los primeros módulos, con gRPC, REST o GraphQL según el consumidor;\n5. documentar la estrategia y los primeros módulos para que el equipo pudiera continuar la transformación.\n\n## Alternativa descartada\n\nReescribir el monolito entero, o mantener servicios nuevos atados a la misma base y a las mismas relaciones compartidas. La primera opción pararía el negocio; la segunda preservaría el acoplamiento que la migración necesitaba reducir.\n\n## Resultado",
      "description": "Modernización incremental de un monolito PHP sin documentación en Flapper: base como fuente de descubrimiento, bounded contexts y Strangler. La separación por dominios redujo en aproximadamente el 25% el número de tablas.",
      "keywords": [
        "monolito",
        "para",
        "base",
        "migración",
        "necesitaba",
        "contexto",
        "reglas",
        "dominio",
        "servicios",
        "tablas"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "flapper-modernizacao",
        "slug": "modernizacion-monolito-sin-documentacion",
        "title": "Flapper: descubrir el dominio antes de separar el monolito",
        "description": "Modernización incremental de un monolito PHP sin documentación en Flapper: base como fuente de descubrimiento, bounded contexts y Strangler. La separación por dominios redujo en aproximadamente el 25% el número de tablas.",
        "company": "Flapper",
        "role": "Ingeniero de Software Full Stack",
        "period": "sep/2021 – jun/2022",
        "excerpt": "El producto no podía parar, y los autores originales ya no estaban. Antes de migrar, fue preciso descubrir qué fronteras aún revelaba la base.",
        "proofValue": "~25%",
        "proofLabel": "menos tablas en la separación por dominios",
        "featuredClaimId": "flapper-domain-modernization",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "cases/flapper-modernizacao-es.md"
      }
    },
    {
      "id": "e373bd7acc1f3198",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-pt-br",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 4)",
      "content": "Eu começaria a olhar para Go quando a tarefa fosse claramente CPU bound: cálculo em alto volume de dados, processamento de imagem em lote, agregações pesadas, compressão, transformação grande de buffers, simulações ou qualquer rotina em que a máquina passa mais tempo calculando do que esperando resposta externa.\n\nNesse tipo de cenário, goroutines com worker pool dão um controle melhor sobre uso de CPU. Também deixam mais explícita a separação entre unidades de trabalho, sincronização e coleta de resultado.\n\nMas Go também cobra preço. É preciso pensar em granularidade dos chunks, consumo de memória, cancelamento, tratamento de erro, backpressure, limites de workers, contenção e consistência do resultado.\n\nSe a divisão do trabalho for ingênua, o ganho de CPU pode vir acompanhado de estouro de memória ou complexidade desnecessária.\n\n## Contraargumento: Node.js também tem worker threads\n\nExiste um contraargumento justo: Node.js não está limitado ao Event Loop para tudo. Worker threads existem justamente para executar trabalho pesado fora do thread principal. Também há estratégias com filas, processos separados, serviços auxiliares e native addons.\n\nEntão a comparação honesta não é “Node.js não consegue”. Consegue.\n\nA questão é custo de implementação, maturidade da equipe, observabilidade, integração com o sistema existente e quanto esforço vale a pena investir para manter aquele processamento dentro do ecossistema Node.\n\nEm alguns times, usar worker threads pode ser suficiente e mais barato do que introduzir Go. Em outros, separar o processamento CPU bound em um serviço Go pode ser mais simples de operar e escalar.\n\nA decisão não deveria nascer de preferência por linguagem. Deveria nascer da natureza da carga.\n\n## A regra prática que ficou",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "mais",
        "mesmo",
        "tempo",
        "trabalho",
        "loop",
        "problema",
        "pode"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência",
        "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
        "excerpt": "Node.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-pt-br.md"
      }
    },
    {
      "id": "e539fe71b20412b1",
      "url": "https://imrafaeldev.site/en/experience/infosistemas",
      "title": "Infosistemas",
      "content": "- [Home](/en/)\n- Infosistemas\n\n## Infosistemas\n\nMy experience at Infosistemas with messaging, integrations, and mobility platforms.\n\nRole\n\nSenior Software Engineer / Software Architect\n\nPeriod\n\nFeb 2025 to May 2026\n\n## Context\n\nAt Infosistemas, I worked on platforms for rental companies, fleets, automakers, and mobility operations. It was an enterprise environment of integrations, fiscal flows, and high-volume services, where I worked closely with DevOps, SREs, and DBAs.\n\n## How I worked\n\nMy work combined architecture with hands-on delivery. I redesigned flows between microservices, led NestJS and Go integrations, and implemented critical-event traceability with NestJS and MongoDB. I also evolved APIs, investigated security issues, and contributed to digital journeys and webapp components when backend and interface continuity was needed.\n\nThe principle that guided my work was making failures observable and manageable from the design stage. In messaging, I treated durability, retry, idempotency, and consumer control as part of the flow rather than later fixes.\n\n## What I took from it\n\nThis experience expanded my work in systems with many dependencies and specialists. I learned to turn requirements, risks, and operational constraints into decisions that stayed clear through client validation.\n\n## Related case study\n\nThe case study details how I redesigned RabbitMQ messaging to make critical flows more predictable and reduce intermittent failures between microservices.\n\n[Read the full case study](/en/cases/rabbitmq-messaging/)",
      "description": "My experience at Infosistemas with messaging, integrations, and mobility platforms.",
      "keywords": [
        "with",
        "infosistemas",
        "messaging",
        "integrations",
        "worked",
        "flows",
        "work",
        "case",
        "study",
        "experience"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/infosistemas"
      }
    },
    {
      "id": "e54b021571719ec7",
      "url": "https://imrafaeldev.site/artigos/intensivao-golang-avancado",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 5)",
      "content": "func main() {\nvar c Counter\nvar wg sync.WaitGroup\nfor i := 0; i &#x3C; 100; i++ {\nwg.Add(1)\ngo func() {\ndefer wg.Done()\nc.Inc()\n}()\n}\nwg.Wait()\nfmt.Println(\"mutex:\", c.n)\nfmt.Println(\"owner:\", runCounterOwner(100))\n}\n**Quando escolher outra abordagem.** Prefira sync.Mutex/sync.RWMutex para proteger mapas, contadores e caches com acesso concorrente simples; prefira sync.Map apenas quando houver padrão comprovado de muitas leituras e poucas escritas com chaves disjuntas. Prefira channels quando precisar de fila, fan-out/fan-in, timeout via select ou backpressure natural com canal com buffer. Evite expor canais internos como API de uma estrutura com estado pequeno — o mutex mantém a interface síncrona e mais fácil de testar.\n\n## 4. Concorrência limitada e backpressure\n\n**Mecanismo.** Concorrência limitada impõe um teto de trabalhos simultâneos com semáforo (canal com buffer de vagas), pool de workers ou errgroup.Group com limite. Backpressure é o efeito:",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "goroutines",
        "sync",
        "func",
        "contexto",
        "quando",
        "done",
        "mutex",
        "limite",
        "context"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-golang-avancado"
      }
    },
    {
      "id": "e5e0ca92ea5b6696",
      "url": "https://imrafaeldev.site/es/articulos/desenoxidando-logica-group-anagrams",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 4)",
      "content": "Contagem [26]uint8 vs sort\nVetor de frequências a–z vira chave. Contagem custa O(k) por string; sort custava O(k log k). Total: de O(n × k log k) para O(n × k).",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "group",
        "clave",
        "solución",
        "result",
        "problema",
        "sort",
        "groups"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/es/articulos/desenoxidando-logica-group-anagrams"
      }
    },
    {
      "id": "e5f5591be8f879d4",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 2)",
      "content": "That was when we opened the execution plan. The screen returned few indicators, but the query crossed relationships, formed a large intermediate volume, and spent CPU on aggregations before reaching them. The final answer was small. The work to produce it, enormous.\n\nDoubling CPU and RAM had given the database more headroom, but the search still forced it to do everything at once. The plan showed the investigation had to enter the path traveled by the query.\n\n## What I need to see before touching the database\n\nFor the database to become a real suspect, I want the execution plan and metrics pointing that way. Query time, reads, and cardinality usually confirm or eliminate a hypothesis in minutes.\n\nIf those measures are healthy, I rule the database out. I have caught slow APIs with the query responding within expectations, while the delay sat in application processing and sequential remote calls. From there, continuing to hunt a database defect would only insist on the wrong layer.\n\nBefore going deeper, I run a short radar over the query and the code. One query to load the list followed by another per record calls for a query count. That N+1 shows up often when the ORM leaves relationships for the backend to fetch one by one.\n\nA JOIN multiplying rows before aggregation calls for measuring intermediate volume. A missing index calls for the plan. A filter applying a function over the indexed column too.\n\nIn code, I look for remote calls or queries with `await` inside a `for`. Each wait may look small alone and still dominate total time when all run in a queue.\n\nNone of these signals closes the diagnosis. An N+1 on a route with two items may have irrelevant impact, a new index may help little on a low-selectivity column, and a voluminous join may be needed to produce the result. So I count queries, time the whole loop, and check in the plan how many rows and reads were produced.",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "e6d979b73c22fa8a",
      "url": "https://imrafaeldev.site/en/articles/backend-performance-close-to-data",
      "title": "Most backend performance problems start close to the data (Part 3)",
      "content": "None of these signals closes the diagnosis. An N+1 on a route with two items may have irrelevant impact, a new index may help little on a low-selectivity column, and a voluminous join may be needed to produce the result. So I count queries, time the whole loop, and check in the plan how many rows and reads were produced.\n\nThat radar only picks the first measurement. The next investigation layer comes from the math.\n\n## One correct line can waste the index\n\nOne of those lines often passes review unnoticed. It returns a full day’s records.\n\nWHERE CAST(datetime_column AS date) = @date\nThe screen result looks correct. In the plan, the story may differ. Applying CAST to the column forces the query to transform values before comparison. With an index on datetime_column, that can prevent a direct range seek, raise reads substantially, and even lead to a scan.\n\nWhen the request is for a full day’s records, I compute the boundaries outside the column.\n\nWHERE datetime_column >= @start_of_day\nAND datetime_column &#x3C; @start_of_next_day\nIf the start is July 16 at midnight, the next bound is July 17 at midnight. That includes every value on the 16th, including those with fractional seconds at the end, without depending on 23:59:59.999.\n\nThe result stays correct, but now there is a range the index can walk. Confirmation comes from comparing reads and the access operator in both plans. If the metric does not change, the hypothesis did not hold.\n\n## Read the plan by the work sequence\n\nAfter comparing filter versions, I read the plan as a story of the work produced by the query. I start with total time and reads. If the screen returns ten indicators but execution performs hundreds of thousands of reads, there is a bill to explain.\n\nThen I compare estimated vs actual cardinality. When the optimizer expected few rows and received many, it may have picked joins, memory, and aggregations for a far smaller scenario than it met at runtime.",
      "description": "Before adding machines, cache, or queues, measure the request",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "before",
        "with",
        "where",
        "start",
        "column",
        "data"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/en/articles/backend-performance-close-to-data"
      }
    },
    {
      "id": "e6e6c2e17a79bdb9",
      "url": "https://imrafaeldev.site/artigos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 3)",
      "content": "Mas a parte importante da história não é “Go resolveu”. A parte importante é que Go resolveu um lado do problema e revelou outro.\n\n## O gargalo pode mudar de lugar\n\nA primeira dificuldade foi garantir que o resultado em Go fosse igual ao resultado em Node.js. Isso é menos glamouroso do que falar sobre concorrência, mas é o que separa otimização real de regressão mascarada.\n\nSe o cálculo fica mais rápido e muda o resultado financeiro, a melhoria não vale nada.\n\nDepois disso, o problema principal virou a divisão dos chunks. A estratégia inicial consumia memória demais. Em testes locais, com datasets menores, o crescimento proporcional chegou perto de 15% em alguns momentos.\n\nO incômodo vinha de uma expectativa errada: eu achei que mudar para Go automaticamente resolveria o problema. Na prática, eu só tinha movido o gargalo de lugar. Antes o limite estava mais claro na CPU. Depois, a estratégia de particionamento começou a pressionar memória.\n\nIsso pode acontecer por vários motivos: cópias desnecessárias, buffers grandes, slices mantendo referência para arrays maiores, filas internas grandes demais ou excesso de trabalho sendo preparado antes de ser processado.\n\nNo resultado final, a memória ainda cresceu cerca de 5%. Nesse caso, o ganho de tempo e CPU compensou a perda. Mas isso não é uma regra universal. Se a carga de produção fosse muito maior, ou se o serviço estivesse rodando com margem pequena de memória, essa troca poderia deixar de ser aceitável.\n\nParalelismo custa coordenação, alocação, sincronização e observabilidade. Não existe execução paralela grátis.\n\n## Quando eu manteria Node.js\n\nEu manteria Node.js sem incômodo para orquestração de I/O: comunicação por WebSocket, chamadas para várias APIs, consultas em banco, disparo de eventos, integração entre serviços e fluxos onde o tempo morto está na espera.",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "loop",
        "mesmo",
        "trabalho",
        "event",
        "problema",
        "mais",
        "tempo"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/artigos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "e7966818be57aac7",
      "url": "https://imrafaeldev.site/experiencias/vbet",
      "title": "VBET",
      "content": "- [Início](/)\n- VBET\n\n## VBET\n\nMinha experiência na VBET com segurança, analytics e performance em escala.\n\nCargo\n\nEngenheiro Backend Sênior\n\nPeríodo\n\nout/2023 a fev/2025\n\n## O contexto\n\nNa VBET, trabalhei em um produto de analytics para afiliados e influenciadores de iGaming. O sistema calculava métricas financeiras e operacionais em uma plataforma que passou a atender bases de usuários muito maiores do que as previstas originalmente.\n\n## Como atuei\n\nAntes de atacar desempenho, comecei reduzindo riscos de segurança e manutenção na API legada. Substituí consultas inseguras, organizei a base com Clean Architecture e injeção de dependências e estabeleci testes e documentação para sustentar as próximas mudanças.\n\nDepois, tratei a performance em etapas. Usei Go, goroutines, channels e consultas paralelas para reduzir a primeira etapa do cálculo de comissões de cerca de sete para três minutos. Como o SQL Server era externo e não podia ser alterado, desenhei um ETL com checkpoints, agregações pré-calculadas e reconciliação, distinguindo dados provisórios de dados consolidados.\n\n## O que levo\n\nEssa experiência consolidou a forma como tomo decisões de performance: entender o limite real, aceitar a consistência compatível com cada uso e só então escolher a tecnologia que resolve a restrição.\n\n## Case relacionado\n\nO case aprofunda a evolução do dashboard de analytics: da correção de riscos na API à estratégia de ETL, pré-cálculo, reconciliação e cache sobre um SQL Server externo.\n\n[Ler case completo](/casos/analytics-sql-server-externo/)",
      "description": "Minha experiência na VBET com segurança, analytics e performance em escala.",
      "keywords": [
        "vbet",
        "para",
        "analytics",
        "performance",
        "como",
        "case",
        "experiência",
        "segurança",
        "riscos",
        "consultas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/experiencias/vbet"
      }
    },
    {
      "id": "e7b87d4fd430f53f",
      "url": "https://imrafaeldev.site/en/articles/go-intensive",
      "title": "Go intensive: concurrency, resilience, and distributed systems in 30 minutes (Part 2)",
      "content": "func (r Reading) Validate() error {\nif r.DeviceID == \"\" {\nreturn errors.New(\"device_id is required\")\n}\nif r.Value &#x3C; -100 || r.Value > 250 {\nreturn fmt.Errorf(\"%w: %.2f\", ErrOutOfRange, r.Value)\n}\nreturn nil\n}\nThe zero value is often useful. Slices share a backing array until append reallocates; maps have no iteration order and need synchronization for concurrent access. Strings are immutable bytes, usually UTF-8. defer runs in LIFO order, while evaluating its arguments when registered.\n\nInterfaces are satisfied implicitly. Define a small interface where it is consumed. Errors are values: add context with %w and inspect the chain with errors.Is or errors.As. Reserve panic for broken invariants or unrecoverable startup failures.\n\n## Goroutines, channels, and context\n\nA goroutine is not a dedicated OS thread. The runtime schedules it over OS threads. Every goroutine needs a clear owner, stop condition, and wait path.\n\nChannels move work or ownership. Mutexes protect shared state. A buffered channel smooths a temporary speed difference; it does not create unlimited capacity.\n\nfunc enqueue(ctx context.Context, jobs chan&#x3C;- Reading, reading Reading) error {\nselect {\ncase jobs &#x3C;- reading:\nreturn nil\ncase &#x3C;-ctx.Done():\nreturn context.Cause(ctx)\n}\n}\nThe producer closes a channel when it knows no more values will be sent. Sending to a closed channel or closing it twice panics. A nil channel blocks forever, and disables its select case.\n\ncontext.Context carries cancellation, deadlines, and request-scoped metadata. Receive it first, propagate it, call every returned cancel, and do not store it in a struct. Cancellation is cooperative: blocking loops must watch ctx.Done().\n\n## Bound concurrency before memory becomes the limit\n\nOne goroutine per message becomes expensive when a downstream slows down. Queues and heap grow, GC gets busier, and the process may fail before CPU looks full.",
      "description": "A practical Go backend review covering goroutines, context, backpressure, idempotency, Kubernetes, observability, and performance.",
      "keywords": [
        "reading",
        "context",
        "channel",
        "errors",
        "return",
        "value",
        "device",
        "goroutine",
        "work",
        "articles"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/en/articles/go-intensive"
      }
    },
    {
      "id": "e833719c9916a1f0",
      "url": "https://imrafaeldev.site/artigos/desenferrujando-logica-container-with-most-water",
      "title": "Desenferrujando a lógica #02: Container With Most Water (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Desenferrujando a lógica #02: Container With Most Water\n\n## Desenferrujando a lógica #02: Container With Most Water\n\nEu tentei escolher o próximo passo olhando apenas para os vizinhos. Funcionou em alguns casos, mas o problema pedia uma visão mais ampla.\n\n15 de setembro de 2026\n\n- [Performance](/artigos/?topic=performance)\n- [Trade-offs](/artigos/?topic=trade-offs)\n\nDepois de resolver o primeiro exercício da série, continuei no LeetCode para trabalhar a lógica sem pedir uma solução pronta. O desafio 011 é o [Container With Most Water](https://leetcode.com/problems/container-with-most-water/).\n\nRecebemos um array de alturas e precisamos escolher duas linhas que formem o recipiente com a maior área.\n\n## Como calcular a área\n\nSe escolho as posições left e right, a largura é a distância entre elas. A altura do recipiente é limitada pela menor das duas linhas.\n\nárea = min(altura da esquerda, altura da direita) × distância\nPor exemplo, com estas alturas:\n\n[1, 8, 6, 2, 5, 4, 8, 3, 7]\nAs linhas nas posições 1 e 8 têm alturas 8 e 7. A menor altura é 7, e a distância entre elas é 7. Essa combinação produz uma área de 49.\n\n## Minha primeira tentativa\n\nComecei com dois ponteiros, um em cada ponta do array. Depois de calcular a área atual, eu simulava duas possibilidades:\n\n- avançar o ponteiro da esquerda;\n\n- recuar o ponteiro da direita.\n\nEu calculava a área dos dois próximos pares e escolhia o maior. O trecho principal era este:\n\nconst paddingLeftArea =\nMath.min(heights[leftIndex + 1], heights[rigthIndex]) *\n(rigthIndex - leftIndex + 1);\n\nconst paddingRightArea =\nMath.min(heights[leftIndex], heights[rigthIndex - 1]) *\n(rigthIndex - 1 - leftIndex);",
      "description": "LeetCode 11 em JavaScript: uma tentativa baseada nos vizinhos, a revisão da hipótese e a solução de dois ponteiros.",
      "keywords": [
        "área",
        "altura",
        "heights",
        "leftindex",
        "rigthindex",
        "largura",
        "menor",
        "ponteiros",
        "para",
        "maior"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/artigos/desenferrujando-logica-container-with-most-water"
      }
    },
    {
      "id": "e85881bd0bea7284",
      "url": "https://imrafaeldev.site/articles/desenoxidando-logica-container-with-most-water-es",
      "title": "Desenoxidando la lógica #02: Container With Most Water (Part 3)",
      "content": "Fueron tres intentos con respuesta incorrecta antes de llegar a las soluciones aceptadas en JavaScript, Go y TypeScript. Más que contar envíos, yo quería mirar el error, entender la hipótesis que falló e intentarlo de nuevo sin tercerizar todo el razonamiento.\n\n## Complejidad\n\nEl algoritmo recorre el array una vez. En cada iteración, uno de los punteros avanza, entonces la complejidad de tiempo es `O(n)` y la complejidad de espacio es `O(1)`.\n\nEl primer intento también usaba dos punteros, pero hacía trabajo extra para comparar posibilidades futuras. La segunda solución usa una propiedad del problema para descartar con seguridad parte de las combinaciones.\n\nEse fue el ejercicio esta vez: no confundir una elección que parece buena ahora con una decisión que el problema realmente permite justificar.\n\n---\n\n*Serie Desenoxidando la lógica #02 — Container With Most Water. Problema en [leetcode.com/problems/container-with-most-water](https://leetcode.com/problems/container-with-most-water/).*",
      "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
      "keywords": [
        "área",
        "heights",
        "leftindex",
        "rigthindex",
        "altura",
        "punteros",
        "maxarea",
        "javascript",
        "leetcode",
        "mayor"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-desenferrujando-logica-container-with-most-water",
        "slug": "desenoxidando-logica-container-with-most-water",
        "title": "Desenoxidando la lógica #02: Container With Most Water",
        "description": "LeetCode 11 en JavaScript: un intento basado en los vecinos, la revisión de la hipótesis y la solución de dos punteros.",
        "excerpt": "Intenté elegir el próximo paso mirando solo a los vecinos. Funcionó en algunos casos, pero el problema pedía una visión más amplia.",
        "publishedAt": "2026-09-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "articles/desenoxidando-logica-container-with-most-water-es.md"
      }
    },
    {
      "id": "e99a12d97ce4c2e1",
      "url": "https://imrafaeldev.site",
      "title": "Rafael Pereira, engenheiro de software sênior (Part 2)",
      "content": "### [VBET: analytics sobre um SQL Server que não podíamos mudar](/casos/analytics-sql-server-externo/)\n\nO banco era de outro time. O dashboard precisava deixar de depender de um schema que não controlávamos.\n\n[Abrir o case](/casos/analytics-sql-server-externo/)\n\nPipeline de comissões\nCada estágio corresponde a uma decisão incremental documentada no case. Números de outras histórias não entram neste desenho.\n\n[Ver o caso](/casos/analytics-sql-server-externo/)\n\nFlapper set/2021 a jun/2022\n\n~25% menos tabelas na separação por domínios\n\n### [Flapper: descobrir o domínio antes de separar o monólito](/casos/modernizacao-monolito-sem-documentacao/)\n\nO produto não podia parar, e os autores originais já não estavam lá. Antes de migrar, foi preciso descobrir quais fronteiras o banco ainda revelava.\n\n[Abrir o case](/casos/modernizacao-monolito-sem-documentacao/)\n\nDescoberta de domínio antes da migração\nO banco legado revela fronteiras; pessoas, autenticação e aeronaves saem gradualmente para contextos com persistência própria.\n\n[Ver o caso](/casos/modernizacao-monolito-sem-documentacao/)\n\nArtefatos\n\n## Projetos selecionados\n\nRepositório público\n\n### [SMS Manager](/projetos/sms-manager/)\n\nImportar CSV não pode travar a API Nest. A campanha persiste e publica na fila RabbitMQ; consumidores em TypeScript, Go ou Rust gravam o resultado no Mongo. A API não espera a operadora.\n\n[Abrir o projeto](/projetos/sms-manager/)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\nExperimento documentado\n\n### [goc_mcp](/projetos/goc-mcp/)\n\nMaestro (Codex/Cursor) delega via MCP; daemon FIFO e workers OpenCode executam com estado em SQLite. A orquestração funcionou, mas a medição não confirmou redução de custo ou tempo.\n\n[Abrir o projeto](/projetos/goc-mcp/)",
      "description": "Portfólio institucional e hub editorial de Rafael Pereira. Trabalho nos pontos em que sistemas simples deixam de ser simples.",
      "keywords": [
        "não",
        "casos",
        "projetos",
        "abrir",
        "case",
        "decisão",
        "caso",
        "falhas",
        "contato",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 4,
        "sourcePath": "/"
      }
    },
    {
      "id": "ea1025ce960c9be1",
      "url": "https://imrafaeldev.site/artigos/typescript-cleanarch",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- TypeScript Clean Architecture: Core, Adapters e Infra\n\n## TypeScript Clean Architecture: Core, Adapters e Infra\n\nArquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.\n\n15 de março de 2023\n\n- [Arquitetura](/artigos/?topic=arquitetura)\n\nDesenvolvimento de software muda o tempo todo. Arquitetura fraca vira manutenção cara, feature lenta, teste difícil e bug difícil de isolar. Vale investir em uma estrutura que suporte evolução sem reescrever o sistema a cada pressão do negócio.\n\n## Um pouco de história\n\nClean Architecture é o nome que Robert C. Martin (Uncle Bob) deu, em 2012, no livro *Clean Architecture: A Craftsman’s Guide to Software Structure and Design*. A proposta foge da rigidez de arquiteturas acopladas a framework e banco: o núcleo fica estável; detalhes externos mudam. A ideia bebe de DDD, SOLID, Onion Architecture e Hexagonal Architecture.\n\n## Proposta geral\n\nEste artigo descreve a Clean Architecture e uma derivação prática para backends em TypeScript: três camadas — **Core**, **Adapters** e **Infra**.\n\n- **Core** — regra de negócio e entidades do domínio. Camada mais interna.\n\n- **Infra** — conexões externas: repositórios concretos, controllers REST, módulos de DI, boilerplate de framework.\n\n- **Adapters** — intermediação nos dois sentidos. Controller não chama usecase “cru”: passa por um serviço. Usecase não fala com o banco: fala com um protocolo que um adapter (repositório, connector, handler) implementa.\n\nCada camada tem capacidades e restrições diferentes; SOLID pesa mais no Core. Serve para CRUD HTTP e para sistemas com vários frameworks e canais.\n\nBenefícios concretos: responsabilidades claras (leitura e manutenção), flexibilidade para trocar plugin sem reescrever regra, e testes isolados por camada.\n\n## Guia de camadas",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "name",
        "string",
        "core",
        "promise",
        "userentity",
        "async",
        "para",
        "this",
        "regra",
        "export"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/artigos/typescript-cleanarch"
      }
    },
    {
      "id": "ea35a46f7ad9325f",
      "url": "https://imrafaeldev.site/en/contact",
      "title": "Contact",
      "content": "- [Home](/en/)\n- Contact\n\nContact\n\n## Dealing with a system that's stopped being simple?\n\nReach out directly via @imrafaeldev — no form. For professional conversation, start on LinkedIn; to see decisions in code, head to GitHub.\n\n- [Instagram @imrafaeldev](https://www.instagram.com/imrafaeldev/)\n- [YouTube @imrafaeldev](https://www.youtube.com/@imrafaeldev)\n- [GitHub @imrafaeldev](https://github.com/imrafaeldev)\n- [LinkedIn @imrafaeldev](https://www.linkedin.com/in/imrafaeldev/)",
      "description": "Talk to Rafael Pereira via @imrafaeldev on LinkedIn, GitHub, Instagram, and YouTube. No form: pick a channel and reach out directly.",
      "keywords": [
        "imrafaeldev",
        "https",
        "linkedin",
        "github",
        "contact",
        "instagram",
        "youtube",
        "home",
        "dealing",
        "with"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/contact"
      }
    },
    {
      "id": "ea4588e3faf12a0f",
      "url": "https://imrafaeldev.site/en/experience/south-system-quiq-itau",
      "title": "South System, QUIQ, and Itaú",
      "content": "- [Home](/en/)\n- South System (assigned to QUIQ/Itaú)\n\n## South System (assigned to QUIQ/Itaú)\n\nMy experience with a white-label, multi-tenant marketplace for financial institutions.\n\nRole\n\nBackend Engineer\n\nPeriod\n\nJun 2022 to Apr 2023\n\n## Context\n\nAt South System, I was assigned to QUIQ to work on a Marketplace as a Service for financial institutions. Itaú was the first context, but the product needed to accept new banks without requiring a fork for each client.\n\n## How I worked\n\nI participated in architecture, data modeling, technology choices, and rule refinement with product. The result was a white-label, multi-tenant platform with logical tenant isolation and Hexagonal Architecture to keep the domain apart from specific integrations.\n\nI worked with Node.js, TypeScript, MySQL, asynchronous Go services, and AWS. I structured unit and integration testing for critical cases and used static analysis as part of the quality workflow.\n\n## What I took from it\n\nThis experience changed how I communicate architecture. I started treating alignment with product, the Product Owner, and stakeholders as part of the technical decision rather than a later step after code.",
      "description": "My experience with a white-label, multi-tenant marketplace for financial institutions.",
      "keywords": [
        "with",
        "product",
        "south",
        "system",
        "assigned",
        "quiq",
        "itaú",
        "architecture",
        "experience",
        "white-label"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/south-system-quiq-itau"
      }
    },
    {
      "id": "ea7d944f01911f1a",
      "url": "https://imrafaeldev.site/artigos/intensivao-go",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 5)",
      "content": "Fan-out de workers quebra ordem global. Em telemetria, o requisito costuma ser ordem por dispositivo. Particione por uma chave estável, como hash(device_id) % N, e processe cada partição de forma sequencial. Guarde observed_at, ingested_at, sequence, event_id e, quando existir, boot_id. O relógio do dispositivo pode estar errado ou reiniciar.\n\nProjete a cadeia para entrega *at least once*. Um fluxo seguro recebe o evento, valida envelope e versão, verifica a chave de idempotência, grava",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "reading",
        "não",
        "para",
        "return",
        "cancelamento",
        "value",
        "quando",
        "context",
        "concorrência",
        "errors"
      ],
      "metadata": {
        "chunkIndex": 4,
        "totalChunks": 5,
        "sourcePath": "/artigos/intensivao-go"
      }
    },
    {
      "id": "ea8eca243d9eeefe",
      "url": "https://imrafaeldev.site/experiences/flapper-pt-br",
      "title": "Flapper | Rafael Pereira",
      "content": "## O contexto\n\nNa Flapper, trabalhei em uma plataforma de aviação executiva cujo produto principal era um monólito PHP com mais de sete anos, pouca documentação útil e sem os desenvolvedores originais disponíveis para explicar o sistema.\n\n## Como atuei\n\nO desafio não era trocar PHP por TypeScript. A aplicação estava em produção e sustentava o negócio, então comecei pela descoberta do domínio. Usei o banco de dados para mapear relações, identificar bounded contexts e planejar uma migração incremental baseada no padrão Strangler.\n\nMigrei módulos como pessoas, autenticação e aeronaves para serviços em Node.js, NestJS e Go. Para reduzir dependências relacionais entre contextos, trabalhamos com projeções locais e eventos no Kafka; gRPC, REST e GraphQL foram aplicados conforme a necessidade de cada integração.\n\n## O que levo\n\nFoi uma experiência que consolidou minha visão de modernização de legado: tecnologia vem depois de entender fronteiras, riscos e uma sequência que preserve a operação. Além de entregar módulos, documentei decisões e conduzi workshops para que o time pudesse continuar a transformação.",
      "description": "Minha experiência na Flapper com modernização incremental de um legado em produção.",
      "keywords": [
        "para",
        "como",
        "módulos",
        "contexto",
        "flapper",
        "trabalhei",
        "plataforma",
        "aviação",
        "executiva",
        "cujo"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "experience-flapper",
        "slug": "flapper",
        "title": "Flapper | Rafael Pereira",
        "description": "Minha experiência na Flapper com modernização incremental de um legado em produção.",
        "company": "Flapper",
        "role": "Engenheiro de Software Full Stack",
        "period": "set/2021 a jun/2022",
        "caseSlug": "modernizacao-monolito-sem-documentacao",
        "caseSummary": "O case mostra como investiguei o domínio a partir de banco e código e conduzi a modernização incremental do monólito sem interromper a operação.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/flapper-pt-br.md"
      }
    },
    {
      "id": "eb96814d9fe83316",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 9)",
      "content": "server := &http.Server{Addr: \":8080\", Handler: mux}\n\n\tctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)\n\tdefer stop()\n\n\tgo func() {\n\t\tfmt.Println(\"ouvindo em :8080\")\n\t\tif err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\t\tfmt.Println(\"erro:\", err)\n\t\t}\n\t}()\n\n\t<-ctx.Done() // sinal recebido: parar de aceitar, drenar o resto\n\tshutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\t_ = server.Shutdown(shutdownCtx)\n\tfmt.Println(\"encerrado com graça\")\n}\n```\n\n**Quando escolher outra abordagem.** O exemplo acima cobre só o HTTP; se houver workers de fundo, acompanhe-os com `sync.WaitGroup` e drenagem adicional antes de concluir o shutdown. Para CLIs e jobs de lote sem rede, basta propagar o contexto de sinal às etapas e aguardar o `WaitGroup` — sem servidor HTTP. Em orquestradores que enviam `SIGKILL` após o período de graça, dimensione o timeout de shutdown abaixo do limite da plataforma (por exemplo, `terminationGracePeriod`). Se trabalhos em voo não puderem ser interrompidos com segurança, prefira drenagem com checkpoint e retomada em vez de tentar estender o timeout indefinidamente.\n\n## 7. Observabilidade: logs, métricas e traces que explicam o sistema\n\n**Mecanismo.** Observabilidade combina três sinais com o mesmo vocabulário de rótulos: logs estruturados para eventos discretos (com identificador de correlação), métricas para comportamento agregado (contadores, histogramas de latência, gauges de fila e de goroutines) e traces para seguir uma requisição através de goroutines e serviços. Em Go, isso significa propagar o identificador pelo `context`, expor métricas no formato do coletor usado e instrumentar fronteiras (HTTP, fila, banco) em vez de cada função interna.",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 8,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "ed04eabf4754e368",
      "url": "https://imrafaeldev.site/es/curriculum",
      "title": "Currículum (Part 3)",
      "content": "Desarrollé microservicios en Node.js y NestJS y automaticé reglas, cálculos y validaciones posventa; el cambio redujo un 34% la necesidad de intervención manual del soporte.\n\n[Leer la experiencia completa](/es/experiencias/maxmilhas/)\n\n- jun/2022 a abr/2023 Remoto South System (asignado a QUIQ/Itaú) Ingeniero Backend Expandir experiencia Replegar experiencia\nDiseñé un marketplace multi-tenant con Node.js y servicios asíncronos en Go, estructurando pruebas críticas con aproximadamente un 95% de cobertura.\n\nContexto\n\nEl trabajo se enfocó en un marketplace white-label y multi-tenant para instituciones financieras, preparado para incorporar nuevos bancos sin forks por cliente.\n\nContribución\n\nParticipé en la arquitectura y el refinamiento de reglas, usando Node.js y servicios asíncronos en Go para aislar variaciones por tenant; estructuré pruebas críticas con aproximadamente un 95% de cobertura.\n\n[Leer la experiencia completa](/es/experiencias/south-system-quiq-itau/)\n\n- sep/2021 a jun/2022 Remoto Flapper Ingeniero de Software Full Stack Expandir experiencia Replegar experiencia\nMigré módulos de personas, autenticación y aeronaves a servicios en Node.js, NestJS y Go; la separación por dominios redujo aproximadamente un 25% el número de tablas.\n\nContexto\n\nLa plataforma de aviación ejecutiva dependía de un monolito de más de siete años, con poca documentación y reglas difíciles de separar sin interrumpir la operación.\n\nContribución\n\nMapeé fronteras de dominio y conduje una modernización incremental, migrando módulos a servicios en Node.js, NestJS y Go; la separación redujo aproximadamente un 25% el número de tablas.\n\n[Leer la experiencia completa](/es/experiencias/flapper/)\n\n- ene/2021 a ago/2021 Remoto Sustentec Ingeniero de Software Full Stack Pleno Expandir experiencia Replegar experiencia\nMantuve y evolucioné un sistema de gestión de laboratorios en Java, Spring Boot y Angular, implementando pruebas de integración antes inexistentes.\n\nContexto",
      "description": "Currículum online de Rafael Pereira, ingeniero backend senior con experiencia en Node.js, Go y Java, y trabajo complementario con React y Angular.",
      "keywords": [
        "experiencia",
        "nestjs",
        "para",
        "ingeniero",
        "node",
        "backend",
        "remoto",
        "expandir",
        "replegar",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 4,
        "sourcePath": "/es/curriculum"
      }
    },
    {
      "id": "ed18142b38061902",
      "url": "https://imrafaeldev.site/es/proyectos/md2cv",
      "title": "md2cv (Part 2)",
      "content": "- Currículos: Markdown, versiones inmutables, restauración rastreable y panorama de evolución.\n\n- ATS: diagnósticos estructurales y puntuación orientativa; PDF textual marcado y DOCX semántico.\n\n- Candidaturas: empresa, vacante, currículo base, versión y estado en el mismo contexto.\n\n- Agentes: máquina de estados para preguntas, intentos y propuesta de nueva versión bajo schema; solo persiste con confirmación.\n\n- Datos: importación y exportación versionada del grafo completo; compilador Markdown (unified/remark) compartido por preview, auditoría y export.\n\nFlujo canónico: perfil → currículo base → versión inmutable → auditoría ATS → PDF/DOCX. La rama de candidatura pasa por agente local supervisado antes del currículo adaptado.\n\n## Estado actual\n\nRepositorio público bajo MIT, portal de documentación y releases x64 para Linux (AppImage, .deb, .rpm) y Windows (NSIS y portable), con CI y pruebas Vitest y Playwright en persistencia, agentes, ATS y flujos Electron.\n\nEl export profesional usado en este sitio nace de ese producto y no es leído por el sitio en runtime.\n\n## Limitaciones\n\nNo sustituye la revisión humana ni promete contratación o aprobación automática por plataformas de reclutamiento. La adaptación a vacantes depende de respuestas confirmadas; el sistema se niega a fabricar experiencia.\n\nLos binarios Windows de esta fase no poseen firma de código. El proyecto es autoral y sin finalidad comercial del mantenedor; la licencia MIT permite reutilización en sus términos.",
      "description": "Estudio desktop local-first para perfil profesional, currículos Markdown, versiones inmutables, ATS y adaptación a vacantes con agentes supervisados. Los datos quedan en el SQLite de la máquina.",
      "keywords": [
        "grafo",
        "perfil",
        "producto",
        "agente",
        "bajo",
        "candidatura",
        "currículo",
        "versión",
        "proyectos",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/es/proyectos/md2cv"
      }
    },
    {
      "id": "ed73958ea51ae667",
      "url": "https://imrafaeldev.site/en/projects/sms-manager",
      "title": "SMS Manager (Part 1)",
      "content": "- [Home](/en/)\n- [Projects](/en/projects/)\n- SMS Manager\n\nPublic repository\n\n## SMS Manager\n\nImporting CSV must not block the Nest API. The campaign persists and publishes to RabbitMQ; consumers in TypeScript, Go, or Rust record the result in Mongo. The API does not wait for the carrier.\n\n[Repository](https://github.com/imrafaeldev/sms-manager)\n\nDesacoplamento da campanha\nCSV na API Nest com persistência Postgres; publicação na fila; consumidores no mesmo contrato gravam no Mongo. A API não espera a operadora.\n\n- [01Problem](#problem)\n- [02Constraints](#constraints)\n- [03Decision](#decision)\n- [04Current state](#current-state)\n- [05Limitations](#limitations)\n\n## Problem\n\nSMS campaigns start from CSV files, users, companies, and authentication between services. Validating and persisting in the same process that fires thousands of messages couples the API to the pace of the carrier and the queue.\n\n## Constraints\n\nThe environment must be reproducible. Authentication between services cannot depend on JWT without revocation. Consumers in more than one language exist to compare the same messaging contract.\n\n## Decision\n\nSeven applications: NestJS user/auth and company/campaign APIs; equivalent consumers in Node.js/TypeScript, Go, and Rust; declarative provisioner for exchanges, queues, and bindings; CSV bulk generator. PostgreSQL/TypeORM for relational API data; MongoDB for consumption results; Redis for opaque revocable token cache; gRPC for authentication between services; RabbitMQ with topic exchanges for batches. The API publishes and moves on without blocking on the carrier pace.\n\n## Current state\n\nPublic repository with Docker Compose for PostgreSQL, MongoDB, Redis, and RabbitMQ. The architecture separates domain, application, and infrastructure in the users, authentication, and companies contexts; the three consumers implement the same message contract.\n\n## Limitations",
      "description": "Decoupled SMS campaigns: the Nest API persists and publishes, the queue delivers, and consumers in TypeScript, Go, or Rust record the result. Opaque token, Redis, and gRPC between services.",
      "keywords": [
        "consumers",
        "carrier",
        "authentication",
        "repository",
        "rabbitmq",
        "between",
        "services",
        "same",
        "contract",
        "with"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/en/projects/sms-manager"
      }
    },
    {
      "id": "ee75d412fd4f2467",
      "url": "https://imrafaeldev.site/es/articulos/backend-performance-cerca-de-los-datos",
      "title": "La mayoría de los problemas de performance de backend empieza cerca de los datos (Part 2)",
      "content": "## Casi duplicamos la máquina y el dashboard siguió lento\n\nFue lo que pasó en un dashboard en el que trabajé. Cargaba de forma síncrona, y una única consulta hacía todo a la vez: buscaba los datos, cruzaba varias informaciones y calculaba los valores exhibidos. Como la CPU de la base quedaba muy alta, casi duplicamos CPU y RAM.\n\nLa base quedó más grande. El dashboard siguió pasando del minuto para cargar. La misma consulta aún concentraba todo el trabajo pesado en una única ejecución.\n\nFue cuando abrimos el plan de ejecución. La pantalla devolvía pocos indicadores, pero la consulta atravesaba relaciones, formaba un volumen intermedio grande y gastaba CPU en agregaciones antes de llegar a ellos. La respuesta final era pequeña. El trabajo para producirla, enorme.\n\nDuplicar CPU y RAM había dado más aire a la base, pero la búsqueda seguía obligándola a hacer todo a la vez. El plan mostró que la investigación necesitaba entrar en el camino recorrido por la consulta.\n\n## Lo que necesito ver antes de tocar la base\n\nPara que la base se vuelva sospechosa de verdad, quiero el plan de ejecución y las métricas apuntando en esa dirección. Tiempo de la consulta, lecturas y cardinalidad suelen confirmar o eliminar una hipótesis en pocos minutos.\n\nSi esas medidas están saludables, saco la base del frente. Ya tomé API lenta con la consulta respondiendo dentro de lo esperado, mientras el retraso estaba en el procesamiento de la aplicación y en llamadas remotas hechas de forma secuencial. A partir de ahí, seguir buscando un defecto en la base sería solo insistir en la capa equivocada.\n\nAntes de profundizar, paso un radar corto por la consulta y por el código. Una query para cargar la lista seguida de otra para cada registro pide un conteo de consultas. Ese N+1 aparece con frecuencia cuando el ORM deja las relaciones para que el backend las busque una a una.",
      "description": "Antes de subir máquina, caché o cola, mide el trabajo de la petición. Plan de ejecución, N+1, CAST en columna y loops secuenciales.",
      "keywords": [
        "base",
        "consulta",
        "para",
        "plan",
        "puede",
        "antes",
        "ejecución",
        "datos",
        "trabajo",
        "cuando"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/backend-performance-cerca-de-los-datos"
      }
    },
    {
      "id": "ef2b08f2b69be8a8",
      "url": "https://imrafaeldev.site",
      "title": "Rafael Pereira, engenheiro de software sênior (Part 1)",
      "content": "Rafael Pereira / engenheiro de software / sistemas\n\n## Eu trabalho nos pontos em que sistemas simples *deixam de ser simples*.\n\nEstudos de caso, projetos e textos técnicos sobre escala, falhas, legado e regras de negócio. Cada peça mostra a restrição, a decisão e até onde a solução funciona.\n\n[Ver os estudos de caso](/casos/) [Ver formas de contato](/contato/)\n\n[Case em foco **Infosistemas** ~98% · redução de falhas intermitentes nos fluxos críticos Abrir o case](/casos/mensageria-rabbitmq/) [**VBET** ~7 min → <1 s](/casos/analytics-sql-server-externo/)\n\n- [~98% Infosistemas: redução de falhas intermitentes nos fluxos críticos](/casos/mensageria-rabbitmq/)\n- [~7 min → <1 s VBET: comissões, da carga original ao cache quente](/casos/analytics-sql-server-externo/)\n- [~25% Flapper: menos tabelas na separação por domínios](/casos/modernizacao-monolito-sem-documentacao/)\n\nSinal\n\nMétodo\n\n## A restrição vem antes do diagrama.\n\nComeço pelo que acontece quando uma mensagem falha.\n\nO banco não pode mudar. O legado não pode parar.\n\nO caminho feliz vem depois.\n\nA decisão registra a alternativa descartada e a condição que justificaria revê-la.\n\nO código mostra o que foi feito; o case mostra por quê.\n\nProva\n\n## Três sistemas, três restrições\n\nInfosistemas fev/2025 a mai/2026\n\n~98% redução de falhas intermitentes nos fluxos críticos\n\n### [Infosistemas: contrato de falha na mensageria RabbitMQ](/casos/mensageria-rabbitmq/)\n\nFalhas intermitentes entre microsserviços sem contrato para retry, DLQ ou duplicidade. Mais consumidores só empurravam a sobrecarga.\n\n[Abrir o case](/casos/mensageria-rabbitmq/)\n\nContrato de falha na mensageria\nPublicação confirmada, consumo com prefetch controlado, retry com backoff e DLQ por fluxo. Escalar só consumidores fica de fora do desenho.\n\n[Ver o caso](/casos/mensageria-rabbitmq/)\n\nVBET out/2023 a fev/2025\n\n~7 min → <1 s comissões, da carga original ao cache quente",
      "description": "Portfólio institucional e hub editorial de Rafael Pereira. Trabalho nos pontos em que sistemas simples deixam de ser simples.",
      "keywords": [
        "não",
        "casos",
        "projetos",
        "abrir",
        "case",
        "decisão",
        "caso",
        "falhas",
        "contato",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 4,
        "sourcePath": "/"
      }
    },
    {
      "id": "f0a1d8cdbf2ff0e5",
      "url": "https://imrafaeldev.site/es/articulos/go-intensivo",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 4)",
      "content": "Varios workers rompen el orden global. Telemetría suele necesitar orden por dispositivo, así que particiona por una clave estable como hash(device_id) % N y procesa cada partición de forma secuencial. Guarda observed_at, ingested_at, sequence, event_id y boot_id cuando exista. El reloj del dispositivo puede desfasarse o reiniciarse.\n\nDiseña la cadena para entrega *at least once*. Recibe el evento, valida envelope y versión del schema, comprueba la clave de idempotencia, persiste efecto y marcador de deduplicación en la misma transacción cuando sea posible y recién entonces hace ACK. Una transactional outbox cierra la ventana entre confirmar el estado en base y publicar el evento siguiente. El consumer sigue necesitando idempotencia porque las duplicatas pueden ocurrir.\n\nRetry sirve para fallos transitorios. Un payload inválido o una regla de negocio rechazada no mejora con otro intento. Añade límite, budget total y jitter para que las réplicas no repitan juntas.\n\nEn el borde usa TLS, identidad por dispositivo, autorización por tópico, límite estricto de payload y validación antes de asignar estructuras grandes. Rotación, revocación, secuencia, nonce y ventana temporal importan cuando el protocolo debe resistir replay.\n\n## Kubernetes, observabilidad y rendimiento\n\nEn SIGTERM, quita readiness, deja de buscar trabajo, drena el trabajo en vuelo dentro del grace period, confirma solo los mensajes concluidos y cierra productores, conexiones y telemetría al final. Liveness pregunta si el proceso progresa y no debe depender de cada servicio externo. Readiness pregunta si ese pod puede aceptar trabajo ahora.\n\nPara escalar consumers, CPU por sí sola es una señal débil. Observa lag, edad del mensaje más antiguo, tasa de llegada, tiempo de procesamiento y ocupación del pool.",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "return",
        "context",
        "puede",
        "orden",
        "channel",
        "concurrencia",
        "más",
        "value"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/go-intensivo"
      }
    },
    {
      "id": "f10b9fb413b7fd3c",
      "url": "https://imrafaeldev.site/en/projects/md2cv",
      "title": "md2cv (Part 2)",
      "content": "- ATS: structural diagnostics and guiding scores; marked textual PDF and semantic DOCX.\n\n- Applications: company, job post, base resume, version, and state in the same context.\n\n- Agents: state machine for questions, attempts, and proposed new versions under schema; persists only on confirmation.\n\n- Data: versioned import and export of the full graph; Markdown compiler (unified/remark) shared by preview, audit, and export.\n\nCanonical flow: profile → base resume → immutable version → ATS audit → PDF/DOCX. The application branch goes through a supervised local agent before the adapted resume.\n\n## Current state\n\nPublic repository under MIT, documentation portal, and x64 releases for Linux (AppImage, .deb, .rpm) and Windows (NSIS and portable), with CI and Vitest and Playwright tests on persistence, agents, ATS, and Electron flows.\n\nThe professional export used on this site comes from this product and is not read by the site at runtime.\n\n## Limitations\n\nIt does not replace human review nor promise hiring or automatic approval by recruiting platforms. Job matching depends on confirmed answers; the system refuses to fabricate experience.\n\nWindows binaries in this phase are not code-signed. The project is author-owned with no commercial purpose from the maintainer; the MIT license allows reuse under its terms.",
      "description": "Local-first desktop studio for professional profiles, Markdown resumes, immutable versions, ATS, and job matching with supervised agents. Data stays in the machine",
      "keywords": [
        "export",
        "with",
        "product",
        "profile",
        "under",
        "graph",
        "state",
        "resume",
        "projects",
        "md2cv"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/projects/md2cv"
      }
    },
    {
      "id": "f19bcba9b704fb87",
      "url": "https://imrafaeldev.site/articles/go-intensivo-es",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 4)",
      "content": "Retry sirve para fallos transitorios. Un payload inválido o una regla de negocio rechazada no mejora con otro intento. Añade límite, budget total y jitter para que las réplicas no repitan juntas.\n\nEn el borde usa TLS, identidad por dispositivo, autorización por tópico, límite estricto de payload y validación antes de asignar estructuras grandes. Rotación, revocación, secuencia, nonce y ventana temporal importan cuando el protocolo debe resistir replay.\n\n## Kubernetes, observabilidad y rendimiento\n\nEn `SIGTERM`, quita readiness, deja de buscar trabajo, drena el trabajo en vuelo dentro del grace period, confirma solo los mensajes concluidos y cierra productores, conexiones y telemetría al final. Liveness pregunta si el proceso progresa y no debe depender de cada servicio externo. Readiness pregunta si ese pod puede aceptar trabajo ahora.\n\nPara escalar consumers, CPU por sí sola es una señal débil. Observa lag, edad del mensaje más antiguo, tasa de llegada, tiempo de procesamiento y ocupación del pool.\n\nUsa logs estructurados y campos de correlación sin registrar credenciales ni payloads sensibles completos. Mide throughput, errores por clase, p50/p95/p99, lag, edad del evento, retries, DLQ, duplicatas, goroutines, heap y pausas de GC. Usa tracing muestreado para cruzar ingesta, stream y persistencia; trazar cada lectura de alta frecuencia puede costar más de lo que ayuda.\n\nUna data race es acceso concurrente a la misma posición de memoria con al menos una escritura y sin orden de sincronización. Envíos por channel, unlock/lock de mutex y operaciones atómicas establecen relaciones de orden. El detector solo cubre rutas ejecutadas:\n\n```bash\ngo test -race ./...\ngo test -bench=. -benchmem ./...\ngo tool pprof cpu.out\ngo tool trace trace.out\n```",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "orden",
        "channel",
        "context",
        "https",
        "return",
        "cada",
        "más",
        "tiempo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-go-intensive",
        "slug": "go-intensivo",
        "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos",
        "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
        "excerpt": "Una guía de 30 minutos para reactivar Go aplicado a servicios de producción, ingestión de telemetría y sistemas distribuidos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 6,
        "sourcePath": "articles/go-intensivo-es.md"
      }
    },
    {
      "id": "f27f47d4638a0685",
      "url": "https://imrafaeldev.site/casos/mensageria-rabbitmq",
      "title": "Infosistemas: contrato de falha na mensageria RabbitMQ (Part 1)",
      "content": "- [Início](/)\n- [Casos](/casos/)\n- Infosistemas: contrato de falha na mensageria RabbitMQ\n\nInfosistemas\n\n## Infosistemas: contrato de falha na mensageria RabbitMQ\n\nFalhas intermitentes entre microsserviços sem contrato para retry, DLQ ou duplicidade. Mais consumidores só empurravam a sobrecarga.\n\nEngenheiro de Software Sênior / Arquiteto de Software fev/2025 a mai/2026\n\n~98% redução de falhas intermitentes nos fluxos críticos\n\nContrato de falha na mensageria\nPublicação confirmada, consumo com prefetch controlado, retry com backoff e DLQ por fluxo. Escalar só consumidores fica de fora do desenho.\n\n- [01Contexto](#contexto)\n- [02Restrições](#restricoes)\n- [03Problema](#problema)\n- [04Decisão](#decisao)\n- [05Alternativa descartada](#alternativa-descartada)\n- [06Implementação](#implementacao)\n- [07Resultado](#resultado)\n- [08Limitações](#limitacoes)\n\n## Contexto\n\nA Infosistemas opera plataformas de gestão para locadoras, frotas e montadoras. O trabalho ocorreu no time de arquitetura, em colaboração com DevOps, SREs e DBAs, entre fevereiro de 2025 e maio de 2026.\n\nEste caso cobre a frente de mensageria. Outras frentes da mesma experiência (segurança de ERP, jornadas de assinatura, integrações fiscais) existem nas fontes, mas não entram aqui como número ou afirmação extra.\n\n## Restrições\n\nOs fluxos críticos cruzavam microsserviços. A falha era intermitente: a mesma operação podia completar numa execução e não completar na seguinte. Aumentar concorrência ou prefetch sem critério transferia sobrecarga para consumidores, serviços ou bancos downstream.\n\n## Problema\n\nMensagens deixavam de completar o fluxo esperado. Investigar falha parcial era difícil. Não havia contrato explícito para falha temporária, falha permanente, duplicidade ou poison message.\n\n## Decisão\n\nA mensageria foi redesenhada para tornar o comportamento em falha previsível:\n\n- filas duráveis;\n\n- DLQ por fluxo, para mensagem que não deve desaparecer nem repetir sem controle;",
      "description": "Redesenho da mensageria RabbitMQ na Infosistemas com filas duráveis, DLQ, retry, idempotência e prefetch. O trabalho reduziu em cerca de 98% as falhas intermitentes entre microsserviços.",
      "keywords": [
        "falha",
        "para",
        "contrato",
        "prefetch",
        "não",
        "mensageria",
        "consumidores",
        "fluxos",
        "imrafaeldev",
        "infosistemas"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 2,
        "sourcePath": "/casos/mensageria-rabbitmq"
      }
    },
    {
      "id": "f2a689a2cf6e64f2",
      "url": "https://imrafaeldev.site/projects/md2cv-es",
      "title": "md2cv (Part 2)",
      "content": "Flujo canónico: perfil → currículo base → versión inmutable → auditoría ATS → PDF/DOCX. La rama de candidatura pasa por agente local supervisado antes del currículo adaptado.\n\n## Estado actual\n\nRepositorio público bajo MIT, portal de documentación y releases x64 para Linux (AppImage, `.deb`, `.rpm`) y Windows (NSIS y portable), con CI y pruebas Vitest y Playwright en persistencia, agentes, ATS y flujos Electron.\n\nEl export profesional usado en este sitio nace de ese producto y no es leído por el sitio en runtime.\n\n## Limitaciones\n\nNo sustituye la revisión humana ni promete contratación o aprobación automática por plataformas de reclutamiento. La adaptación a vacantes depende de respuestas confirmadas; el sistema se niega a fabricar experiencia.\n\nLos binarios Windows de esta fase no poseen firma de código. El proyecto es autoral y sin finalidad comercial del mantenedor; la licencia MIT permite reutilización en sus términos.",
      "description": "Estudio desktop local-first para perfil profesional, currículos Markdown, versiones inmutables, ATS y adaptación a vacantes con agentes supervisados. Los datos quedan en el SQLite de la máquina.",
      "keywords": [
        "currículo",
        "versión",
        "candidatura",
        "grafo",
        "agentes",
        "base",
        "perfil",
        "bajo",
        "producto",
        "entre"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "projeto-md2cv",
        "slug": "md2cv",
        "title": "md2cv",
        "description": "Estudio desktop local-first para perfil profesional, currículos Markdown, versiones inmutables, ATS y adaptación a vacantes con agentes supervisados. Los datos quedan en el SQLite de la máquina.",
        "excerpt": "Perfil y versiones inmutables quedan en el SQLite de la máquina. El agente supervisado solo propone cambios bajo schema; ATS y exportación en PDF/DOCX reutilizan el mismo grafo, sin backend SaaS dueño de los datos.",
        "repoUrl": "https://github.com/imrafaeldev/md2cv",
        "status": "Producto propio, desktop",
        "featured": "true",
        "order": "3",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "projects/md2cv-es.md"
      }
    },
    {
      "id": "f2e746baa4e7e17d",
      "url": "https://imrafaeldev.site/en/experience/vbet",
      "title": "VBET",
      "content": "- [Home](/en/)\n- VBET\n\n## VBET\n\nMy experience at VBET with security, analytics, and performance at scale.\n\nRole\n\nSenior Backend Engineer\n\nPeriod\n\nOct 2023 to Feb 2025\n\n## Context\n\nAt VBET, I worked on an analytics product for iGaming affiliates and influencers. It calculated financial and operational metrics in a platform that began serving audiences much larger than originally expected.\n\n## How I worked\n\nBefore addressing performance, I reduced security and maintenance risks in the legacy API. I replaced unsafe queries, organized the codebase around Clean Architecture and dependency injection, and established tests and documentation to support the following changes.\n\nI then addressed performance in stages. I used Go, goroutines, channels, and parallel queries to reduce the first commission-calculation stage from about seven to three minutes. Since the SQL Server was external and could not be changed, I designed an ETL with checkpoints, pre-calculated aggregates, and reconciliation, separating provisional data from consolidated data.\n\n## What I took from it\n\nThis experience consolidated how I make performance decisions: understand the actual constraint, accept the consistency appropriate to each use, and then choose the technology that addresses it.\n\n## Related case study\n\nThe case study follows the analytics dashboard evolution: from API risk reduction to ETL, pre-computation, reconciliation, and caching over an external SQL Server.\n\n[Read the full case study](/en/cases/external-sql-server-analytics/)",
      "description": "My experience at VBET with security, analytics, and performance at scale.",
      "keywords": [
        "vbet",
        "performance",
        "from",
        "analytics",
        "case",
        "study",
        "experience",
        "with",
        "security",
        "worked"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/vbet"
      }
    },
    {
      "id": "f3ba728e927578b2",
      "url": "https://imrafaeldev.site/curriculo",
      "title": "Currículo (Part 4)",
      "content": "Atuei em sistemas ligados a laboratórios, pesquisa e desenvolvimento, combinando manutenção de produto existente com evolução funcional.\n\nContribuição\n\nMantive e evoluí o sistema com Java, Spring Boot e Angular, implementei testes de integração e entreguei relatórios e funcionalidades ponta a ponta.\n\n[Ler experiência completa](/experiencias/sustentec/)\n\n- nov/2019 a jan/2021 Campina Grande, Paraíba / Remoto Braistech Engenheiro de Software Full Stack Pleno Expandir experiência Recolher experiência\nLiderei a estruturação do sistema principal com Node.js e NestJS e projetei microsserviços para o núcleo do negócio.\n\nContexto\n\nEm um produto com domínio de contratos de criptoativos e movimentações financeiras, assumi responsabilidade ampla em uma equipe pequena.\n\nContribuição\n\nLiderei a estruturação do sistema principal com Node.js e NestJS, projetei microsserviços para o núcleo do negócio e ajudei a evoluir o código para Clean Architecture, além de orientar desenvolvedores juniores.\n\n[Ler experiência completa](/experiencias/braistech/)\n\n## Especialidades\n\n-\n\n### Node.js\n\nEspecialidade em APIs, microsserviços e integrações com NestJS.\n\n-\n\n### Go\n\nExperiência avançada em APIs, concorrência e processamento de alto volume.\n\n-\n\n### Java\n\nExperiência em manutenção e evolução de sistemas com Spring Boot.\n\n-\n\n### React\n\nAtuação na evolução e manutenção de aplicações React.\n\n-\n\n### Angular\n\nAtuação em aplicação web Angular com formulários, validações e consumo de API.\n\n## Formação\n\n-\n\n### Análise e Desenvolvimento de Sistemas\n\nTecnólogo · UNOPAR — Universidade Norte do Paraná\n\nconcluído em 2024\n-\n\n### Computação em Nuvem\n\nPós-graduação · Anhanguera Educacional\n\nconcluída em 2025\n\n## Links públicos\n\n- [LinkedIn — Abrir link público](https://www.linkedin.com/in/this-rafael-pereira/)\n- [GitHub — Abrir link público](https://github.com/this-rafael)",
      "description": "Currículo online de Rafael Pereira, engenheiro backend sênior com experiência em Node.js, Go e Java, e atuação complementar em React e Angular.",
      "keywords": [
        "experiência",
        "para",
        "nestjs",
        "engenheiro",
        "node",
        "backend",
        "remoto",
        "expandir",
        "recolher",
        "contexto"
      ],
      "metadata": {
        "chunkIndex": 3,
        "totalChunks": 4,
        "sourcePath": "/curriculo"
      }
    },
    {
      "id": "f4e6281ba0c5642a",
      "url": "https://imrafaeldev.site/es/casos/analitica-sql-server-externo",
      "title": "VBET: analítica sobre un SQL Server que no podíamos cambiar (Part 1)",
      "content": "- [Inicio](/es/)\n- [Casos](/es/casos/)\n- VBET: analítica sobre un SQL Server que no podíamos cambiar\n\nVBET\n\n## VBET: analítica sobre un SQL Server que no podíamos cambiar\n\nLa base era de otro equipo. El dashboard necesitaba dejar de depender de un schema que no controlábamos.\n\nIngeniero Backend Sénior oct/2023 – feb/2025\n\n~7 min → <1 s comisiones, de la carga original a la caché caliente\n\nPipeline de comissões\nCada estágio corresponde a uma decisão incremental documentada no case. Números de outras histórias não entram neste desenho.\n\n- [01Contexto](#contexto)\n- [02Restricciones](#restricciones)\n- [03Problema](#problema)\n- [04Decisión](#decision)\n- [05Alternativa descartada](#alternativa-descartada)\n- [06Resultado](#resultado)\n- [07Limitaciones](#limitaciones)\n\n## Contexto\n\nEn VBET, entre octubre de 2023 y febrero de 2025, el producto de analítica servía a influencers y afiliados de iGaming. El dashboard reunía decenas de métricas; la comisión era la lectura más crítica. Los influencers aceptaban un pequeño desfase en los datos del día, siempre que la pantalla respondiera. El pago dependía de datos consolidados del día anterior, no del valor en vivo.\n\n## Restricciones\n\nEl SQL Server era externo, compartido y no modificable de forma fiable. Los índices temporales podían ser eliminados por el propietario de la base. La API original mezclaba consultas SQL construidas a partir de parámetros, con riesgo de inyección, y agregaba demasiado en memoria.\n\n## Problema\n\nEl sistema había sido dimensionado para influencers más pequeños. Con bases mayores, el peor pico del dashboard llegó a cerca de siete minutos. Seguridad y mantenibilidad vinieron antes del rendimiento: queries crudas, poca cobertura de pruebas y un camino síncrono que recalculaba demasiado en cada request.\n\n## Decisión\n\nLa evolución fue incremental, en el orden en que aparecieron las restricciones:\n\n- eliminar SQL inseguro, parametrizar el acceso, documentar y probar;",
      "description": "Dashboard de comisiones en VBET: SQL Server externo, ETL propio y caché. La línea medida fue de cerca de siete minutos en el pico hasta menos de un segundo con caché caliente.",
      "keywords": [
        "cerca",
        "imrafaeldev",
        "vbet",
        "server",
        "base",
        "dashboard",
        "caché",
        "cada",
        "índices",
        "minutos"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/es/casos/analitica-sql-server-externo"
      }
    },
    {
      "id": "f55b27a2886c16fc",
      "url": "https://imrafaeldev.site/es/articulos/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 1)",
      "content": "- [Inicio](/es/)\n- [Artículos](/es/articulos/)\n- Design Patterns: Strategy\n\n## Design Patterns: Strategy\n\nLas cadenas de if/else crecen y se vuelven frágiles. Strategy aísla cada algoritmo tras un contrato.\n\n5 de mayo de 2022\n\n- [Arquitectura](/es/articulos/?topic=arquitetura)\n\nLas cadenas de if/else crecen y se vuelven frágiles. El patrón Strategy encapsula cada algoritmo en su propia clase y permite intercambiar implementaciones en tiempo de ejecución sin alterar el código que las consume.\n\n## El problema: if/else infinito\n\nUna calculadora con suma, resta, multiplicación y división suele nacer como una clase DefaultCalculator: métodos privados por operación y una función pública que elige cuál invocar con switch o cadena de if/else.\n\nclass DefaultCalculator {\npublic calculate(parameters: BinaryOperationParameters): Result {\nconst { operator, firstOperand, secondOperand } = parameters;\n\nswitch (operator) {\ncase \"*\":\nreturn firstOperand * secondOperand;\ncase \"+\":\nreturn firstOperand + secondOperand;\ncase \"-\":\nreturn firstOperand - secondOperand;\ncase \"/\":\nreturn firstOperand / secondOperand;\ncase \"**\":\nreturn firstOperand ** secondOperand;\ncase \"%\":\nreturn firstOperand % secondOperand;\ndefault:\nthrow new Error(\"Operator not found!\");\n}\n}\n}\nEl problema aparece cuando la calculadora necesita cubrir más operaciones binarias entre enteros: porcentaje, exponenciación, módulo, shift de bits. Cada funcionalidad nueva altera la implementación original, sube el acoplamiento y encarece el mantenimiento.\n\n## ¿Qué es el patrón Strategy?\n\nEl patrón define la funcionalidad por medio de un contrato (interfaz), implementado según el contexto. La interfaz define la operación; las implementaciones concretas definen su ejecución.\n\nEl código consumidor depende de la abstracción. Cada estrategia queda aislada en su propia clase. Nuevos comportamientos entran sin alterar el código existente, alineado al Open/Closed Principle.\n\n## Definiendo el contrato",
      "description": "Cómo el patrón Strategy encapsula algoritmos intercambiables y evita frágiles cadenas de if/else, con un ejemplo de calculadora en TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "contrato",
        "parameters",
        "operator",
        "cada",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "/es/articulos/design-patterns-strategy"
      }
    },
    {
      "id": "f572714056948921",
      "url": "https://imrafaeldev.site/experiences/vbet-es",
      "title": "VBET | Rafael Pereira",
      "content": "## Contexto\n\nEn VBET, trabajé en un producto de analítica para afiliados e influencers de iGaming. El sistema calculaba métricas financieras y operativas en una plataforma que empezó a atender audiencias mucho mayores de lo previsto originalmente.\n\n## Cómo trabajé\n\nAntes de abordar rendimiento, reduje riesgos de seguridad y mantenimiento en la API legada. Reemplacé consultas inseguras, organicé la base con Clean Architecture e inyección de dependencias y establecí pruebas y documentación para sostener los cambios siguientes.\n\nDespués traté el rendimiento por etapas. Usé Go, goroutines, channels y consultas paralelas para reducir la primera etapa del cálculo de comisiones de cerca de siete a tres minutos. Como el SQL Server era externo y no podía cambiarse, diseñé un ETL con checkpoints, agregaciones precalculadas y reconciliación, diferenciando datos provisionales de datos consolidados.\n\n## Lo que me llevé\n\nEsta experiencia consolidó cómo tomo decisiones de rendimiento: entender la restricción real, aceptar la consistencia adecuada para cada uso y después elegir la tecnología que la resuelve.",
      "description": "Mi experiencia en VBET con seguridad, analítica y rendimiento a escala.",
      "keywords": [
        "para",
        "rendimiento",
        "trabajé",
        "cómo",
        "consultas",
        "después",
        "datos",
        "contexto",
        "vbet",
        "producto"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "experience-vbet",
        "slug": "vbet",
        "title": "VBET | Rafael Pereira",
        "description": "Mi experiencia en VBET con seguridad, analítica y rendimiento a escala.",
        "company": "VBET",
        "role": "Ingeniero Backend Senior",
        "period": "oct/2023 a feb/2025",
        "caseSlug": "analitica-sql-server-externo",
        "caseSummary": "El caso profundiza en la evolución del dashboard de analítica: desde la reducción de riesgos en la API hasta ETL, precálculo, reconciliación y caché sobre un SQL Server externo.",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "experiences/vbet-es.md"
      }
    },
    {
      "id": "f67e6cfbd43a3925",
      "url": "https://imrafaeldev.site/articles/design-patterns-adapter-es",
      "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter (Part 2)",
      "content": "El servicio pierde el conocimiento de cómo el CRUD llega a la base. Está compuesto por protocolos; la implementación concreta entra en tiempo de ejecución — por inyección de dependencias. Mientras usamos PostgreSQL, implementamos los protocolos en los adapters (o connectors).\n\n`CreateDatabaseCustomerProtocol` puede ser implementado por `CreateDatabaseCustomerPostgresqlAdapter`, `CreateDatabaseCustomerMysqlAdapter`, `CreateDatabaseCustomerMongoDBAdapter` o `CreateDatabaseCustomerMockedAdapter`. El servicio queda así:\n\n```typescript\nclass CustomerService {\n  constructor(\n    private readonly createCustomer: CreateDatabaseCustomerProtocol,\n  ) {}\n\n  public register(customer: CustomerInputEntity): SuccessfulEntityCreation {\n    return this.createCustomer.createCustomerOnDatabase(customer);\n  }\n}\n```\n\nPara el servicio, da igual si la base devuelve JSON, XML u otro formato — el adapter traduce al contrato que la regla de negocio espera.\n\n## Ventajas\n\n- **Mantenimiento:** cualquier plugin puede sustituirse sin reescribir el servicio.\n- **Pruebas:** para probar solo la regla de negocio, inyecta un adapter mock que implemente el mismo protocolo.\n- **Código limpio:** responsabilidades separadas; la regla de negocio no carga detalles del driver.\n\n## Próximo paso\n\nToma un frontend con decenas de bibliotecas e identifica lo que realmente usas. Elige una funcionalidad — convertir reales a dólares, por ejemplo. Describe el contrato (entrada y salida) e implementa un adapter sobre la biblioteca que hoy lo hace. Repite donde la dependencia moleste.\n\n## Relación con otros patrones\n\nEn el artículo [Design Patterns: Strategy](/es/articulos/design-patterns-strategy/), el foco es intercambiar algoritmos tras un contrato. El Adapter aísla dependencias externas tras una interfaz propia. Ambos se complementan: Strategy varía comportamiento; Adapter traduce el mundo exterior.\n\n---",
      "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
      "keywords": [
        "adapter",
        "regla",
        "negocio",
        "base",
        "servicio",
        "readonly",
        "contrato",
        "createdatabasecustomerprotocol",
        "customerinputentity",
        "successfulentitycreation"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-design-patterns-adapter",
        "slug": "design-patterns-adapter",
        "title": "Deja de ser rehén de las dependencias. Saluda al patrón de diseño Adapter",
        "description": "Con el Adapter, el servicio depende de un protocolo y los adapters traducen MySQL, PostgreSQL o mocks — sin acoplar la regla de negocio al driver.",
        "excerpt": "Migrar de MySQL a PostgreSQL no exige reescribir el servicio. El Adapter aísla el plugin tras un contrato que la regla de negocio entiende.",
        "publishedAt": "2022-04-26",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 3,
        "sourcePath": "articles/design-patterns-adapter-es.md"
      }
    },
    {
      "id": "f6a61fef1894c3a0",
      "url": "https://imrafaeldev.site/artigos/goroutines-vs-event-loop",
      "title": "Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência (Part 1)",
      "content": "- [Início](/)\n- [Artigos](/artigos/)\n- Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência\n\n## Goroutines vs Event Loop: a comparação errada entre dois modelos de concorrência\n\nNode.js com Event Loop e Go com goroutines não resolvem o mesmo problema do mesmo jeito. O erro comum é confundir concorrência com paralelismo.\n\n24 de junho de 2026\n\n- [Trade-offs](/artigos/?topic=trade-offs)\n- [Performance](/artigos/?topic=performance)\n\nA tese é simples: Node.js com Event Loop e Go com goroutines não resolvem o mesmo tipo de problema do mesmo jeito. A comparação fica ruim quando a gente trata os dois como concorrentes diretos em qualquer cenário. Na prática, o erro mais comum é confundir concorrência com paralelismo.\n\nConcorrência é organizar várias tarefas que podem estar em andamento ao mesmo tempo. Paralelismo é executar trabalho de fato ao mesmo tempo, usando múltiplos núcleos de CPU. Essa diferença parece acadêmica até aparecer em produção.\n\nO Event Loop do Node.js é muito bom quando o gargalo está em espera: API externa, banco de dados, WebSocket, input de usuário, filas e eventos. Enquanto uma operação aguarda resposta, o loop continua atendendo outras tarefas. É o dono da bodega no balcão: ele não para porque pediu a alguém para buscar a rapadura no estoque.\n\nGoroutines, por outro lado, começam a ficar mais interessantes quando o trabalho é CPU bound, divisível e pode aproveitar múltiplos núcleos com controle explícito de concorrência. Elas são unidades leves de execução gerenciadas pelo runtime do Go. Com elas, dá para quebrar uma tarefa em partes menores, distribuir a execução e sincronizar o resultado no final.\n\nÉ mais parecido com uma barraca cheia no São João de Caruaru: uma pessoa assa o milho, outra mexe a canjica, outra corta o bolo de rolo. O trabalho avança ao mesmo tempo, com cada pessoa cuidando de uma parte.\n\n## O ponto onde o Node.js começa a sofrer",
      "description": "Concorrência não é paralelismo. Quando o Event Loop do Node.js basta para I/O e quando goroutines em Go encaixam melhor em carga CPU bound.",
      "keywords": [
        "para",
        "node",
        "não",
        "loop",
        "mesmo",
        "trabalho",
        "event",
        "problema",
        "mais",
        "tempo"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 5,
        "sourcePath": "/artigos/goroutines-vs-event-loop"
      }
    },
    {
      "id": "f6b7065c15373aed",
      "url": "https://imrafaeldev.site/es/experiencias/flapper",
      "title": "Flapper",
      "content": "- [Inicio](/es/)\n- Flapper\n\n## Flapper\n\nMi experiencia en Flapper con modernización incremental de un legado en producción.\n\nCargo\n\nIngeniero de Software Full Stack\n\nPeríodo\n\nsep/2021 a jun/2022\n\n## Contexto\n\nEn Flapper, trabajé en una plataforma de aviación ejecutiva cuyo producto principal era un monolito PHP de más de siete años, con poca documentación útil y sin desarrolladores originales disponibles para explicar el sistema.\n\n## Cómo trabajé\n\nEl desafío no era reemplazar PHP por TypeScript. La aplicación estaba en producción y sostenía el negocio, así que empecé por descubrir el dominio. Usé la base de datos para mapear relaciones, identificar bounded contexts y planificar una migración incremental basada en el patrón Strangler.\n\nMigré módulos como personas, autenticación y aeronaves a servicios en Node.js, NestJS y Go. Para reducir dependencias relacionales entre contextos, trabajamos con proyecciones locales y eventos en Kafka; gRPC, REST y GraphQL se usaron según la necesidad de cada integración.\n\n## Lo que me llevé\n\nEsta experiencia consolidó mi visión de modernización de legado: la tecnología viene después de entender fronteras, riesgos y una secuencia que preserve la operación. Además de entregar módulos, documenté decisiones y conduje workshops para que el equipo continuara la transformación.\n\n## Caso relacionado\n\nEl caso muestra cómo investigué el dominio a partir de la base de datos y el código y conduje la modernización incremental del monolito sin interrumpir la operación.\n\n[Leer el caso completo](/es/casos/modernizacion-monolito-sin-documentacion/)",
      "description": "Mi experiencia en Flapper con modernización incremental de un legado en producción.",
      "keywords": [
        "flapper",
        "para",
        "modernización",
        "incremental",
        "caso",
        "experiencia",
        "legado",
        "producción",
        "trabajé",
        "monolito"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/es/experiencias/flapper"
      }
    },
    {
      "id": "f757149609ce59a6",
      "url": "https://imrafaeldev.site/en/experience/eds-policia-civil-rio",
      "title": "EDS and Rio de Janeiro Civil Police",
      "content": "- [Home](/en/)\n- EDS (Civil Police of Rio de Janeiro)\n\n## EDS (Civil Police of Rio de Janeiro)\n\nMy consulting experience on sensitive public systems.\n\nRole\n\nBackend Engineer — Consulting\n\nPeriod\n\nJul 2025 to Dec 2025\n\n## Context\n\nIn my consulting work for EDS, I worked on systems for the Civil Police of Rio de Janeiro. The context involved a critical public operation, a high-volume health management system, and an evolving legal ERP.\n\n## How I worked\n\nI structured the health system backend with NestJS and SQL Server. I also refactored legacy routes and contributed to flows for process automation, document management, and evidence collection. Security, access control, traceability, and LGPD compliance guided how every route had to evolve.\n\nBeyond backend work, I collaborated on shared design-system components to align API contracts with the interfaces used in the operation.\n\n## What I took from it\n\nThe work reinforced the care needed to evolve sensitive systems without losing auditability. Rather than separating security from delivery, I treated access and traceability as part of the product contract.",
      "description": "My consulting experience on sensitive public systems.",
      "keywords": [
        "civil",
        "police",
        "janeiro",
        "consulting",
        "systems",
        "backend",
        "work",
        "2025",
        "sensitive",
        "public"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/eds-policia-civil-rio"
      }
    },
    {
      "id": "f7c45fdd9d6a1b0d",
      "url": "https://imrafaeldev.site/artigos/design-patterns-strategy",
      "title": "Design Patterns: Strategy (Part 3)",
      "content": "/**\n* A implementação de calculate em Calculator não muda a cada operação.\n* O que cresce é o ContextAnalyzer, que adiciona um case por operação nova.\n*/\npublic calculate(parameters: BinaryOperationParameters): Result {\nconst { operator, firstOperand, secondOperand } = parameters;\nreturn this.contextAnalyzer\n.getInstance(operator)\n.calculate({ firstOperand, secondOperand });\n}\n}\n\n## Por que usar o Strategy?\n\nO Strategy ajuda em legado com várias regras de negócio, cada uma representada por um if e uma implementação extensa. A parte comum fica no contrato; cada variação de regra fica em sua própria classe; um analisador de contexto (resolver/factory) escolhe a estratégia.\n\nA cada requisição, o código avalia o contexto da operação e seleciona a implementação correspondente ao contrato.\n\n## Relação com outros padrões\n\n- Adapter: o Strategy varia comportamento; o Adapter isola dependências externas atrás de uma interface própria.\n\n- SOLID (OCP): o Strategy é uma forma de aplicar o Open/Closed Principle.\n\nStrategy: contrato, seleção e concretas\nCalculator depende do contrato BinaryOperationStrategy. ContextAnalyzer escolhe a concreta (ex.: Sum) pelo operador; Sum, Division e Pow implementam o mesmo contrato.",
      "description": "Como o padrão Strategy encapsula algoritmos intercambiáveis e evita cadeias frágeis de if/else, com um exemplo de calculadora em TypeScript.",
      "keywords": [
        "secondoperand",
        "firstoperand",
        "return",
        "case",
        "strategy",
        "contrato",
        "cada",
        "parameters",
        "operator",
        "contextanalyzer"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 3,
        "sourcePath": "/artigos/design-patterns-strategy"
      }
    },
    {
      "id": "f7f6813a2f0dd62f",
      "url": "https://imrafaeldev.site/es/articulos/go-intensivo",
      "title": "Intensivo de Golang: concurrencia, resiliencia y sistemas distribuidos en 30 minutos (Part 2)",
      "content": "func (r Reading) Validate() error {\nif r.DeviceID == \"\" {\nreturn errors.New(\"device_id is required\")\n}\nif r.Value &#x3C; -100 || r.Value > 250 {\nreturn fmt.Errorf(\"%w: %.2f\", ErrOutOfRange, r.Value)\n}\nreturn nil\n}\nEl zero value suele ser útil. Los slices comparten backing array hasta que append realoca; los maps no tienen orden de iteración y necesitan sincronización para acceso concurrente. Un string contiene bytes inmutables, generalmente UTF-8. defer se ejecuta en orden LIFO, pero evalúa sus argumentos al registrarse.\n\nLas interfaces se satisfacen implícitamente. Define interfaces pequeñas donde se consumen. Los errores son valores: añade contexto con %w e inspecciona la cadena con errors.Is o errors.As. Reserva panic para invariantes rotas o fallos irrecuperables de arranque.\n\n## Goroutines, channels y contexto\n\nUna goroutine no es un hilo dedicado. El runtime la planifica sobre hilos del sistema operativo. Cada goroutine necesita owner, condición de término y alguien que espere su finalización.\n\nLos channels transfieren trabajo u ownership. Los mutexes protegen estado compartido. Un channel con buffer suaviza una diferencia temporal de velocidad; no crea capacidad infinita.\n\nfunc enqueue(ctx context.Context, jobs chan&#x3C;- Reading, reading Reading) error {\nselect {\ncase jobs &#x3C;- reading:\nreturn nil\ncase &#x3C;-ctx.Done():\nreturn context.Cause(ctx)\n}\n}\nEl productor cierra un channel cuando sabe que no habrá más envíos. Enviar a un channel cerrado o cerrarlo dos veces causa panic. Un channel nil bloquea para siempre y deshabilita su caso dentro de select.\n\ncontext.Context propaga cancelación, deadlines y metadatos de la request. Recíbelo primero, propágalo, llama a cada cancel retornado y no lo guardes en una struct. La cancelación es cooperativa: los loops bloqueantes deben observar ctx.Done().\n\n## Limita la concurrencia antes de que la memoria sea el límite",
      "description": "Revisión práctica de Go para backend: goroutines, context, backpressure, idempotencia, Kubernetes, observabilidad y rendimiento.",
      "keywords": [
        "para",
        "reading",
        "return",
        "context",
        "puede",
        "orden",
        "channel",
        "concurrencia",
        "más",
        "value"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/es/articulos/go-intensivo"
      }
    },
    {
      "id": "f7fe188e332de954",
      "url": "https://imrafaeldev.site/articles/intensivao-go-pt-br",
      "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos (Part 6)",
      "content": "Em `SIGTERM`, um consumer deve sair de readiness, parar de buscar trabalho, drenar o que já está em voo dentro do grace period e confirmar apenas o que terminou. O restante volta ao broker. Feche produtores, conexões e telemetria por último.\n\nLiveness responde se o processo progride; não a faça depender de cada serviço externo. Readiness responde se o pod pode receber trabalho agora e pode falhar por overload ou perda de uma dependência obrigatória. Startup protege inicializações lentas.\n\nPara escalar consumers, CPU isolada costuma ser um sinal fraco. Observe lag, idade da mensagem mais antiga, taxa de entrada, duração do processamento e ocupação do pool.\n\nLogs estruturados devem carregar a correlação necessária para investigar uma leitura sem despejar o payload inteiro:\n\n```go\nlogger.InfoContext(ctx, \"reading persisted\",\n\t\"device_id\", reading.DeviceID,\n\t\"sequence\", reading.Sequence,\n\t\"latency_ms\", elapsed.Milliseconds(),\n)\n```\n\nMétricas úteis incluem throughput, erros por classe, p50/p95/p99, lag, idade do evento, espera e ocupação dos workers, retries, DLQ, duplicatas, goroutines, heap e pausas de GC. Use tracing amostrado para atravessar ingestão, stream e persistência. Traçar cada leitura de alta frequência pode custar mais do que a investigação que ele pretende facilitar.\n\n## Runtime e performance\n\nUma data race ocorre quando acessos concorrentes à mesma posição de memória incluem escrita e não são ordenados por sincronização. Envio em channel, `Mutex.Unlock` seguido da aquisição do mesmo lock e operações atômicas fornecem relações de ordem relevantes. O race detector ajuda, mas só encontra caminhos executados:\n\n```bash\ngo test -race ./...\n```\n\nO scheduler trabalha com G (goroutine), M (thread do sistema) e P (recurso lógico de execução). `GOMAXPROCS` limita quantos Ps executam código Go simultaneamente, não quantas goroutines podem existir. Goroutines são leves, mas stacks, referências e scheduling têm custo. Crie trabalho com limite.",
      "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
      "keywords": [
        "não",
        "reading",
        "para",
        "como",
        "context",
        "return",
        "quando",
        "pode",
        "channel",
        "https"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-intensive",
        "slug": "intensivao-go",
        "title": "Intensivão Golang: concorrência, resiliência e sistemas distribuídos em 30 minutos",
        "description": "Revisão prática de Go para backend: goroutines, context, backpressure, idempotência, Kubernetes, observabilidade e performance.",
        "excerpt": "Um roteiro de 30 minutos para reativar Go aplicado a serviços de produção, ingestão de telemetria e sistemas distribuídos.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 5,
        "totalChunks": 8,
        "sourcePath": "articles/intensivao-go-pt-br.md"
      }
    },
    {
      "id": "f88c28ce75246fa1",
      "url": "https://imrafaeldev.site/artigos/backend-performance-perto-dos-dados",
      "title": "A maioria dos problemas de performance de backend começa perto dos dados (Part 3)",
      "content": "No código, procuro chamadas remotas ou consultas com await dentro de for. Cada espera pode parecer pequena isoladamente e ainda assim dominar o tempo total quando todas executam em fila.\n\nNenhum desses sinais fecha o diagnóstico. Um N+1 numa rota com dois itens pode ter impacto irrelevante, um índice novo pode ajudar pouco numa coluna com baixa seletividade e um join volumoso talvez seja necessário para produzir o resultado. Por isso, conto as consultas, cronometro o loop inteiro e confiro no plano quantas linhas e leituras foram produzidas.\n\nEsse radar apenas escolhe a primeira medição. A próxima camada da investigação vem da conta.\n\n## Uma linha correta pode desperdiçar o índice\n\nUma dessas linhas costuma passar batida na revisão. Ela devolve os registros de um dia inteiro.\n\nWHERE CAST(campo_data_hora AS date) = @data\nO resultado da tela parece correto. No plano, a história pode ser outra. Aplicar CAST à coluna obriga a consulta a transformar os valores antes da comparação. Com um índice em campo_data_hora, isso pode impedir uma busca direta pelo intervalo, aumentar bastante as leituras e até levar a um scan.\n\nQuando o pedido é pelos registros de um dia inteiro, calculo as bordas fora da coluna.\n\nWHERE campo_data_hora >= @inicio_do_dia\nAND campo_data_hora &#x3C; @inicio_do_proximo_dia\nSe o início é 16 de julho à meia-noite, o limite seguinte é 17 de julho à meia-noite. Assim entram todos os valores do dia 16, inclusive aqueles com frações de segundo no final, sem depender de 23:59:59.999.\n\nO resultado permanece correto, mas agora existe um intervalo que o índice pode percorrer. A confirmação vem da comparação das leituras e do operador de acesso nos dois planos. Se a métrica não mudar, a hipótese não ficou de pé.\n\n## Leia o plano pela sequência do trabalho",
      "description": "Antes de subir máquina, cache ou fila, meça o trabalho da requisição. Plano de execução, N+1, CAST em coluna e loops sequenciais.",
      "keywords": [
        "para",
        "consulta",
        "banco",
        "plano",
        "pode",
        "antes",
        "execução",
        "dados",
        "não",
        "trabalho"
      ],
      "metadata": {
        "chunkIndex": 2,
        "totalChunks": 5,
        "sourcePath": "/artigos/backend-performance-perto-dos-dados"
      }
    },
    {
      "id": "f89972af71cf7a9d",
      "url": "https://imrafaeldev.site/articles/intensivao-golang-avancado-pt-br",
      "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs (Part 12)",
      "content": "**Quando escolher outra abordagem.** Se o sintoma for memória crescente, comece pelo perfil de heap (`/debug/pprof/heap`) e pelo gráfico de goroutines antes do perfil de CPU — vazamento de goroutine aparece como contagem que nunca cai. Para contenção de locks, ative `runtime.SetMutexProfileFraction` e `SetBlockProfileRate` em ambiente de teste, não permanentemente em produção. Se o gargalo estiver fora do processo (banco lento, rede, broker), profiling local não ajuda: volte à observabilidade (seção 7) e meça latência por fronteira antes de micro-otimizar Go.\n\n## Referências oficiais\n\n- Runtime do Go 1.14 (preempção assíncrona de goroutines): https://go.dev/doc/go1.14#runtime\n- `runtime.GOMAXPROCS`: https://pkg.go.dev/runtime#GOMAXPROCS\n- Pacote `context`: https://pkg.go.dev/context\n- Context e cancelamento: https://go.dev/blog/context\n- Effective Go: https://go.dev/doc/effective_go\n- `sync.Mutex`: https://pkg.go.dev/sync#Mutex\n- `sync.Map`: https://pkg.go.dev/sync#Map\n- `errgroup`: https://pkg.go.dev/golang.org/x/sync/errgroup\n- `time.NewTimer`: https://pkg.go.dev/time#NewTimer\n- `http.Server.Shutdown`: https://pkg.go.dev/net/http#Server-Shutdown\n- `signal.NotifyContext`: https://pkg.go.dev/os/signal#NotifyContext\n- `log/slog`: https://pkg.go.dev/log/slog\n- `net/http/pprof`: https://pkg.go.dev/net/http/pprof\n- `runtime/pprof`: https://pkg.go.dev/runtime/pprof\n- Diagnóstico de programas Go: https://go.dev/doc/diagnostics\n- Encerramento de pods (pod termination): https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination",
      "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
      "keywords": [
        "para",
        "func",
        "context",
        "http",
        "limite",
        "sync",
        "time",
        "quando",
        "goroutines",
        "done"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-go-advanced-intensive",
        "slug": "intensivao-golang-avancado",
        "title": "Intensivão Golang Avançado: aprofundamento em concorrência, arquitetura e trade-offs",
        "description": "Aprofundamento do roteiro de 30 minutos de Go: limites de concorrência, desenho de consumers, idempotência e trade-offs de produção.",
        "excerpt": "O passo seguinte ao roteiro rápido de Go, com aprofundamento guiado em concorrência, arquitetura e trade-offs.",
        "publishedAt": "2026-09-22",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 11,
        "totalChunks": 12,
        "sourcePath": "articles/intensivao-golang-avancado-pt-br.md"
      }
    },
    {
      "id": "f99ccc39f73b9a1e",
      "url": "https://imrafaeldev.site/articles/backend-performance-perto-dos-dados-en",
      "title": "Most backend performance problems start close to the data (Part 1)",
      "content": "A slow API and the meeting already fills with solutions before anyone has a measurement. More CPU and RAM show up, cache, queues, microservices, refactoring. Sometimes someone even proposes switching languages. Rarely is the first suggestion to open the execution plan.\n\nThat is why I start the investigation close to the data. Not because the database is the default culprit, but because so much passes through it and that check usually gives a fast signal. If the query is healthy, I rule the database out and follow the request flow.\n\n## Quick fixes can also hide expensive work\n\nCache and queues solve real problems. Bigger machines can also be the right call. When they arrive by reflex, though, those resources may only change the bill size while nobody knows which part of the request is holding the response.\n\nMore CPU reduces resource contention, cache takes some requests out of the path, and a queue absorbs a spike. Meanwhile, a query doing enormous work to return almost nothing stays expensive on every execution. The same goes for a loop firing one call per item.\n\nLatency may drop for a while, the alert stops firing, and the team breathes. When load grows, the expensive operation reappears, now accompanied by larger infrastructure.\n\nThe question that must come before the architecture debate: how much work is this request producing to deliver the result?\n\n## We almost doubled the machine and the dashboard stayed slow\n\nThat is what happened with a dashboard I worked on. It loaded synchronously, and a single query did everything at once: fetched data, joined several pieces of information, and computed the displayed values. Since database CPU ran very high, we almost doubled CPU and RAM.\n\nThe database got bigger. The dashboard still took over a minute to load. The same query still concentrated all heavy work in a single execution.",
      "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
      "keywords": [
        "that",
        "query",
        "database",
        "plan",
        "with",
        "before",
        "after",
        "reads",
        "when",
        "time"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-backend-performance-data",
        "slug": "backend-performance-close-to-data",
        "title": "Most backend performance problems start close to the data",
        "description": "Before adding machines, cache, or queues, measure the request's work. Execution plans, N+1, CAST on columns, and sequential loops.",
        "excerpt": "Slow API and the meeting already fills with solutions. I start close to the data — not to blame the database, but because that check usually gives a fast signal.",
        "publishedAt": "2026-07-16",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 7,
        "sourcePath": "articles/backend-performance-perto-dos-dados-en.md"
      }
    },
    {
      "id": "f9e92c5ce926f6cd",
      "url": "https://imrafaeldev.site/en/experience/braistech",
      "title": "Braistech",
      "content": "- [Home](/en/)\n- Braistech\n\n## Braistech\n\nMy experience at Braistech with product, microservices, and crypto assets.\n\nRole\n\nMid-level Full Stack Software Engineer\n\nPeriod\n\nNov 2019 to Jan 2021\n\n## Context\n\nAt Braistech, I had one of my first product experiences in a small environment with a distributed level of responsibility. The domain involved crypto-asset contracts and financial movements.\n\n## How I worked\n\nI led the structure of the main system with Node.js and NestJS, took part in designing microservices for the business core, and developed Flutter applications. I also built a contract system and worked on payment integrations related to the Binance ecosystem.\n\nI participated in a transition from an MVC organization toward Clean Architecture. The goal was to reduce coupling and make a growing system easier to maintain, while I guided junior developers through the code decisions.\n\n## What I took from it\n\nThis stage consolidated my interest in backend and architecture. Working across the full product also gave me a full-stack perspective that remains useful in conversations with frontend and product teams.",
      "description": "My experience at Braistech with product, microservices, and crypto assets.",
      "keywords": [
        "braistech",
        "with",
        "product",
        "system",
        "microservices",
        "full",
        "worked",
        "took",
        "also",
        "from"
      ],
      "metadata": {
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "/en/experience/braistech"
      }
    },
    {
      "id": "fb1cceca585c9896",
      "url": "https://imrafaeldev.site/en/cases/undocumented-monolith-modernization",
      "title": "Flapper: discovering the domain before splitting the monolith (Part 2)",
      "content": "- propagate changes through Kafka events, instead of connecting every service to the old database;\n\n- use Node.js, NestJS, and Go in the first modules, with gRPC, REST, or GraphQL depending on the consumer;\n\n- document the strategy and the first modules so the team could continue the transformation.\n\n## Discarded alternative\n\nRewriting the whole monolith, or keeping new services tied to the same database and shared relationships. The first option would stop the business; the second would preserve the coupling the migration needed to reduce.\n\n## Result\n\nThe domain-based split reduced the table count by approximately 25% and created seven databases organized by context. The people, authentication, and aircraft modules were the first steps of a transformation planned to continue beyond the initial delivery.\n\n## Limitations\n\nThe number measures the table reduction in that domain split, not financial gain, the complete migration, or a market result for the company. The experience end date diverges across older sources; the published period follows the exported profile. If a domain still depends on rules unmapped in the monolith, its extraction must be postponed or given an explicit transitional integration.\n\nContact\n\n## Dealing with a system that's stopped being simple?\n\nReach out directly via @imrafaeldev, no form — for professional conversation, start on LinkedIn.\n\n[Instagram](https://www.instagram.com/imrafaeldev/)[YouTube](https://www.youtube.com/@imrafaeldev)[GitHub](https://github.com/imrafaeldev)[LinkedIn](https://www.linkedin.com/in/imrafaeldev/)\n\n[Open the contact page](/en/contact/)",
      "description": "Incremental modernization of an undocumented PHP monolith at Flapper: database as discovery source, bounded contexts, and Strangler. The domain-based split reduced the table count by approximately 25%.",
      "keywords": [
        "domain",
        "monolith",
        "database",
        "imrafaeldev",
        "flapper",
        "before",
        "could",
        "were",
        "context",
        "with"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "/en/cases/undocumented-monolith-modernization"
      }
    },
    {
      "id": "fb854e1ed239bbe6",
      "url": "https://imrafaeldev.site/articles/typescript-cleanarch-pt-br",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 4)",
      "content": "Classes que implementam protocols. Cada uma adapta **um** dispositivo externo (ORM, cliente HTTP, fila, filesystem).\n\nConvenção de nomes:\n\n- **Repositories** — protocol ligado a banco (vocabulário familiar).\n- **Connectors** — retornam dados sem ser “tabela” (ex.: `ClientHttpFetchConnector`, `ClientHttpAxiosConnector`).\n- **Handlers** — processam sem retorno síncrono (ex.: publicar em Kafka).\n\nOutros nomes são válidos; o critério é um adapter por dispositivo.\n\nNo CRUD, só repository (mock):\n\n```typescript\n// adapters/repositories/UsersMockRepository.ts\nexport class UsersMockRepository\n  implements\n    GetUserByIdProtocol,\n    GetUserByNameProtocol,\n    CreateUserProtocol,\n    UpdateUserProtocol,\n    DeleteUserProtocol\n{\n  private db: DbConnector;\n\n  constructor() {\n    this.db = mockDbConnector;\n  }\n\n  async getById(id: string): Promise<UserEntity> {\n    return this.db.users.getById(id);\n  }\n\n  async getByName(name: string): Promise<UserEntity | null> {\n    return this.db.users.getByName(name);\n  }\n\n  async register(name: string): Promise<UserEntity> {\n    return this.db.users.register(name);\n  }\n\n  async update(id: string, name: string): Promise<UserEntity> {\n    return this.db.users.update(id, name);\n  }\n\n  async delete(id: string): Promise<void> {\n    return this.db.users.delete(id);\n  }\n}\n```\n\nMock do conector:",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "string",
        "name",
        "promise",
        "core",
        "userentity",
        "this",
        "async",
        "export",
        "class",
        "typescript"
      ],
      "metadata": {
        "locale": "pt-BR",
        "translationKey": "article-typescript-cleanarch",
        "slug": "typescript-cleanarch",
        "title": "TypeScript Clean Architecture: Core, Adapters e Infra",
        "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
        "excerpt": "Arquitetura ruim trava manutenção, testes e mudança. Esta derivação da Clean Architecture para backend TypeScript separa Core, Adapters e Infra — com dependências apontando para dentro.",
        "publishedAt": "2023-03-15",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 3,
        "totalChunks": 9,
        "sourcePath": "articles/typescript-cleanarch-pt-br.md"
      }
    },
    {
      "id": "fc2b1329e0287a38",
      "url": "https://imrafaeldev.site/artigos/typescript-cleanarch",
      "title": "TypeScript Clean Architecture: Core, Adapters e Infra (Part 2)",
      "content": "Exemplo: CRUD de usuários via REST com NestJS. Detalhes de instalação ficam de fora. Escrita **core-to-infra** (de dentro para fora).\n\n## Core\n\nNo desenho clássico, *domain* e *entities* ficam muito próximas. Aqui elas formam o **Core**: tudo o que a regra de negócio *é* — funcionalidades e representações do domínio.\n\nNo exemplo, a entidade principal é Usuário (id, name), em core/entities.\n\n### Entities\n\n// core/entities/UserEntity.ts\nexport interface UserEntityProps {\nid?: string;\nname: string;\n}\n\nexport class UserEntity {\nconstructor(private readonly props: UserEntityProps) {}\n\nget id(): string {\nreturn this.props.id ?? \"\";\n}\n\nget name(): string {\nreturn this.props.name;\n}\n}\nA entidade recebe props tipadas e expõe getters. Depende de uma interface que qualquer DTO de transferência pode satisfazer depois.\n\n### Features e usecases\n\nO CRUD precisa criar, buscar, atualizar e remover. No Core, cada usecase implementa um contrato (feature) com um único método público — alinhado a Liskov, aberto/fechado, segregação de interface e responsabilidade única. O usecase **não** acessa o banco: conhece **protocols** que descrevem a ação externa (inversão de dependência).\n\nCadastro: nome obrigatório; se já existir, erro; se não, retorna UserEntity.\n\n- contrato CreateUser\n\n- implementação CreateUserUsecase\n\nNo TypeScript, classe abstrata com métodos abstratos funciona como contrato *e* valor — útil para DI (const createUserSymbol = CreateUser):\n\n// core/features/CreateUser.ts\nexport abstract class CreateUser {\nabstract execute(name: string): Promise&#x3C;UserEntity>;\n}\n// core/usecases/CreateUserUsecase.ts\nexport class CreateUserUsecase implements CreateUser {\nconstructor(\nprivate readonly createUserProtocol: CreateUserProtocol,\nprivate readonly getByNameProtocol: GetUserByNameProtocol,\n) {}\n\nasync execute(name: string): Promise&#x3C;UserEntity> {\nconst existsName = await this.getByNameProtocol.getByName(name);",
      "description": "Derivação da Clean Architecture para backend TypeScript: Core com usecases e protocols, Adapters bidirecionais e Infra NestJS com injeção de dependências.",
      "keywords": [
        "name",
        "string",
        "core",
        "promise",
        "userentity",
        "async",
        "para",
        "this",
        "regra",
        "export"
      ],
      "metadata": {
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "/artigos/typescript-cleanarch"
      }
    },
    {
      "id": "fccbbe63c1cdb498",
      "url": "https://imrafaeldev.site/cases/vbet-analytics-en",
      "title": "VBET: analytics on top of a SQL Server we could not change (Part 2)",
      "content": "- initial worst peak: about 7 minutes;\n- after queries and indexes: about 3 minutes;\n- after parallelization: about 1 minute;\n- after ETL/PostgreSQL: about 15 seconds at commission p99 without cache;\n- warm cache: under 1 second.\n\nEach number belongs to its stage. It does not describe the gain of a later decomposition into microservices.\n\n## Limitations\n\nThe investigation, the ETL + owned database decision, the REALTIME/CLOSED split, and the degradation policy are the attributable core here. Decomposing the monolith into Kubernetes is a separate story and does not mix the \"500%\" nor sub-60 ms latency into this case. Older resume versions citing 30 seconds on cold load, 11 seconds, or SLA percentages without a scenario are left out. If the product starts requiring realtime accuracy for payout, the CLOSED split stops being enough.",
      "description": "Commission dashboard at VBET: external SQL Server, owned ETL, and cache. The measured line went from about seven minutes at peak to under one second with a warm cache.",
      "keywords": [
        "about",
        "queries",
        "with",
        "indexes",
        "minutes",
        "realtime",
        "influencers",
        "dashboard",
        "commission",
        "data"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "vbet-analytics",
        "slug": "external-sql-server-analytics",
        "title": "VBET: analytics on top of a SQL Server we could not change",
        "description": "Commission dashboard at VBET: external SQL Server, owned ETL, and cache. The measured line went from about seven minutes at peak to under one second with a warm cache.",
        "company": "VBET",
        "role": "Senior Backend Engineer",
        "period": "Oct/2023 – Feb/2025",
        "excerpt": "The database belonged to another team. The dashboard had to stop depending on a schema we did not control.",
        "proofValue": "~7 min → <1 s",
        "proofLabel": "commissions, from the original load to the warm cache",
        "featuredClaimId": "vbet-commission-performance",
        "featured": "true",
        "order": "2",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/vbet-analytics-en.md"
      }
    },
    {
      "id": "fde78d6017c1f642",
      "url": "https://imrafaeldev.site/cases/infosistemas-mensageria-en",
      "title": "Infosistemas: a failure contract for RabbitMQ messaging (Part 2)",
      "content": "The recorded reduction in intermittent failures in critical flows between microservices was approximately 98%. The number describes those flows after the redesign, not the entire company operation nor other tracks.\n\n## Limitations\n\nMetrics from other tracks that are still pending method or confirmation are left out. If volume or the microservice map changes so that DLQ and prefetch no longer isolate failure, tuning must be revisited with queue and consumer telemetry.",
      "description": "RabbitMQ messaging redesign at Infosistemas with durable queues, DLQ, retry, idempotency, and prefetch. The work reduced intermittent failures between microservices by about 98%.",
      "keywords": [
        "failure",
        "with",
        "prefetch",
        "flows",
        "that",
        "other",
        "tracks",
        "critical",
        "without",
        "consumers"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "infosistemas-mensageria",
        "slug": "rabbitmq-messaging",
        "title": "Infosistemas: a failure contract for RabbitMQ messaging",
        "description": "RabbitMQ messaging redesign at Infosistemas with durable queues, DLQ, retry, idempotency, and prefetch. The work reduced intermittent failures between microservices by about 98%.",
        "company": "Infosistemas",
        "role": "Senior Software Engineer / Software Architect",
        "period": "Feb/2025 – May/2026",
        "excerpt": "Intermittent failures between microservices with no contract for retry, DLQ, or duplication. Adding more consumers only pushed the overload elsewhere.",
        "proofValue": "~98%",
        "proofLabel": "fewer intermittent failures in critical flows",
        "featuredClaimId": "infosistemas-rabbitmq-reliability",
        "featured": "true",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 1,
        "totalChunks": 2,
        "sourcePath": "cases/infosistemas-mensageria-en.md"
      }
    },
    {
      "id": "fe347233cfbb2f7a",
      "url": "https://imrafaeldev.site/articles/goroutines-vs-event-loop-en",
      "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models (Part 2)",
      "content": "That is the kind of scenario where `Promise.all` can mislead. It gives a sense of parallelism, but it does not automatically turn heavy CPU work into real parallel execution. If the tasks are compute-intensive and run on the same main thread, the Event Loop stays busy.\n\nThe result can be worse than expected: loop blocking, higher latency, worse responsiveness, and more pressure on CPU and memory.\n\nThe problem was not Node.js being bad. The problem was using the default Node model for a load that demanded another kind of execution.\n\n## Where Go fit better\n\nThe solution was rewriting that process in Go with goroutines. The idea was to split the calculation into smaller chunks, process those pieces in parallel, and synchronize only at the end.\n\nThat design fit the problem better because the work was CPU bound and divisible. Instead of a centralized flow trying to coordinate several heavy operations, processing became distributed across smaller execution units.\n\nWith a worker pool, for example, you can control the goroutine count, limit fan-out, use available cores better, and avoid firing unbounded work.\n\nThe gain showed up. Total time dropped about 25%. The process that sat around 30 seconds started running near 22.5 seconds. CPU usage also improved.\n\nBut the important part of the story is not \"Go fixed it\". The important part is that Go fixed one side of the problem and revealed another.\n\n## The bottleneck can move\n\nThe first difficulty was guaranteeing the Go result equaled the Node.js result. That is less glamorous than talking about concurrency, but it is what separates real optimization from masked regression.\n\nIf the calculation gets faster and changes the financial result, the improvement is worthless.\n\nAfter that, the main problem became chunk splitting. The initial strategy consumed too much memory. In local tests, with smaller datasets, proportional growth reached near 15% at some points.",
      "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
      "keywords": [
        "that",
        "with",
        "node",
        "work",
        "loop",
        "problem",
        "event",
        "time",
        "memory",
        "same"
      ],
      "metadata": {
        "locale": "en",
        "translationKey": "article-goroutines-vs-event-loop",
        "slug": "goroutines-vs-event-loop",
        "title": "Goroutines vs Event Loop: the wrong comparison between two concurrency models",
        "description": "Concurrency is not parallelism. When the Node.js Event Loop is enough for I/O and when Go goroutines fit CPU-bound load better.",
        "excerpt": "Node.js with the Event Loop and Go with goroutines do not solve the same problem the same way. The common mistake is confusing concurrency with parallelism.",
        "publishedAt": "2026-06-24",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 1,
        "totalChunks": 5,
        "sourcePath": "articles/goroutines-vs-event-loop-en.md"
      }
    },
    {
      "id": "ff276fecd548e9a5",
      "url": "https://imrafaeldev.site/visual-projects/gran-goias-es",
      "title": "Gran Goiás",
      "content": "Sitio institucional de Gran Goiás, construido en torno a la ejecución en piedra para obras de escala.",
      "description": "Sitio institucional para Gran Goiás, con ejecución en piedra para obras de escala.",
      "keywords": [
        "sitio",
        "institucional",
        "gran",
        "goiás",
        "construido",
        "torno",
        "ejecución",
        "piedra",
        "para",
        "obras"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "visual-gran-goias",
        "slug": "gran-goias",
        "title": "Gran Goiás",
        "description": "Sitio institucional para Gran Goiás, con ejecución en piedra para obras de escala.",
        "excerpt": "Sitio institucional de Gran Goiás, marmolería con ejecución en piedra para obras de escala.",
        "siteUrl": "https://gran-goias.vercel.app/",
        "imageUrl": "https://gran-goias.vercel.app/_astro/lavatorio-duplo-iluminado.DyCNyxPd_Z4lorq.webp",
        "imageAlt": "Lavabo doble iluminado en piedra clara con vetas.",
        "imageWidth": "1448",
        "imageHeight": "1086",
        "order": "1",
        "indexable": "true",
        "chunkIndex": 0,
        "totalChunks": 1,
        "sourcePath": "visual-projects/gran-goias-es.md"
      }
    },
    {
      "id": "ff97c300ba3a34fe",
      "url": "https://imrafaeldev.site/articles/desenferrujando-logica-group-anagrams-es",
      "title": "Desenoxidando la lógica #01: Group Anagrams (Part 1)",
      "content": "Después de años programando, percibí que mi lógica estaba oxidada. Yo no había dejado de escribir código. Lo que cambió fue que, poco a poco, empecé a tercerizar partes del razonamiento que antes necesitaba ejercitar solo.\n\nElegí el ejercicio [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/) en LeetCode. La propuesta es recibir una lista de strings y reunir los anagramas en el mismo grupo.\n\n## Lo que necesitamos resolver\n\nEntrada:\n\n```text\n[\"eat\", \"tea\", \"tan\", \"ate\", \"nat\", \"bat\"]\n```\n\nResultado posible:\n\n```text\n[\"eat\", \"tea\", \"ate\"]\n[\"tan\", \"nat\"]\n[\"bat\"]\n```\n\nEl orden de los grupos no importa.\n\n## ¿Qué es un anagrama?\n\nToma `eat`, `tea` y `ate`. Cada una tiene `a` una vez, `e` una vez y `t` una vez. La posición cambia; la cantidad de cada letra sigue igual.\n\nEl algoritmo necesita transformar esas palabras en una representación común. Si las tres producen la misma clave, puedo usar esa clave en un `map` y colocarlas en el mismo grupo. El primer problema es crear esa clave.\n\n## Mi primera respuesta fue ordenar\n\nEmpecé usando el string ordenado como clave:\n\n```text\neat → aet\ntea → aet\nate → aet\n```\n\nLas tres producen `aet`.\n\n### Solución usando sort\n\nEsta fue la primera solución que se me ocurrió. No intentaba la implementación más concisa de inmediato. Quería montar una solución coherente y entender dónde podría mejorar.\n\n```go\nfunc sortString(str string) string {\n\tb := []byte(str)\n\tslices.Sort(b)\n\treturn string(b)\n}\n\nfunc groupAnagrams(strs []string) [][]string {\n\tmapping := make(map[string][]string)\n\n\tfor _, str := range strs {\n\t\tsortedStr := sortString(str)\n\t\tmapping[sortedStr] = append(mapping[sortedStr], str)\n\t}\n\n\tresult := make([][]string, 0, len(mapping))\n\n\tfor _, group := range mapping {\n\t\tresult = append(result, group)\n\t}\n\n\treturn result\n}\n```\n\nLo que ocurre en ese código:",
      "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
      "keywords": [
        "string",
        "para",
        "cada",
        "clave",
        "solución",
        "result",
        "problema",
        "groups",
        "group",
        "mismo"
      ],
      "metadata": {
        "locale": "es",
        "translationKey": "article-desenferrujando-logica-group-anagrams",
        "slug": "desenoxidando-logica-group-anagrams",
        "title": "Desenoxidando la lógica #01: Group Anagrams",
        "description": "LeetCode 49 en Go: de la clave por sort al conteo de 26 letras, y el hábito de seguir pensando después de que el código funciona.",
        "excerpt": "Yo no había dejado de escribir código. Lo que cambió fue tercerizar partes del razonamiento. Group Anagrams fue el ejercicio para recuperar el hábito.",
        "publishedAt": "2026-08-17",
        "indexable": "true",
        "topics": "",
        "chunkIndex": 0,
        "totalChunks": 3,
        "sourcePath": "articles/desenferrujando-logica-group-anagrams-es.md"
      }
    }
  ],
  "metadata": {
    "totalEntries": 411,
    "generator": "aeo.js",
    "generatorUrl": "https://aeojs.org",
    "embedding": {
      "recommended": "text-embedding-ada-002",
      "dimensions": 1536
    }
  }
}