{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling","title":"Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript","description":"Un análisis de Effect-TS: efectos algebraicos para TypeScript, concurrencia estructurada, errores tipados, scheduling basado en Fibers, y por qué reemplazar el caos del try-catch con un sistema de efectos real hace el código dramáticamente más confiable.","language":"es","datePublished":"2025-11-27","dateModified":null,"contentFormat":"text/markdown","content":"TypeScript\n\n# Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript\n\n2025-11-279 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/effect-ts-algebraic-effects-error-handling>)\n\n## Qué Es Realmente Effect-TS\n\n[Effect](<https://effect.website/>) es una librería TypeScript que te da un solo tipo, ` Effect<Success, Error, Requirements> `, que representa una computación que:\n\n- Produce un valor de tipo ` Success `\n- Puede fallar con un error de tipo ` Error `\n- Requiere servicios de tipo ` Requirements ` para ejecutarse\n\nEl insight clave: los errores están tipados. Cuando llamás a una función que retorna ` Effect<Order, PaymentError | NotFoundError, Database | Stripe> `, sabés exactamente qué puede salir mal, y el compilador te fuerza a manejarlo.\n\nEste es el puntotá el mismo procesamiento de orden reescrito con Effect:\n\n```typescript\nimport { Effect, pipe } from \"effect\"\n\nclass OrderNotFound extends Data.TaggedError(\"OrderNotFound\")<{\n orderId: string\n}> {}\n\nclass PaymentFailed extends Data.TaggedError(\"PaymentFailed\")<{\n orderId: string\n status: string\n}> {}\n\nclass EmailFailed extends Data.TaggedError(\"EmailFailed\")<{\n email: string\n cause: unknown\n}> {}\n\nconst processOrder = (orderId: string) =>\n pipe(\n // Paso 1: Buscar la orden\n db.orders.findById(orderId),\n Effect.flatMap((order) =>\n order === null\n ? Effect.fail(new OrderNotFound({ orderId }))\n : Effect.succeed(order)\n ),\n\n // Paso 2: Cobrar pago\n Effect.flatMap((order) =>\n pipe(\n stripe.charges.create({\n amount: order.total,\n currency: \"usd\",\n source: order.paymentMethodId,\n }),\n Effect.flatMap((payment) =>\n payment.status !== \"succeeded\"\n ? Effect.fail(new PaymentFailed({ orderId, status: payment.status }))\n : Effect.succeed({ order, payment })\n )\n )\n ),\n\n // Paso 3: Actualizar estado de la orden\n Effect.flatMap(({ order, payment }) =>\n pipe(\n db.orders.update(orderId, { status: \"paid\", paymentId: payment.id }),\n Effect.map(() => ({ order, payment }))\n )\n ),\n\n // Paso 4: Enviar recibo (no crítico, catcheamos y logueamos)\n Effect.tap(({ order }) =>\n pipe(\n emailService.sendReceipt(order.customerEmail, order),\n Effect.catchAll((cause) =>\n Effect.logWarning(\"Email de recibo falló\", { cause, orderId })\n )\n )\n ),\n\n Effect.map(({ order }) => order)\n )\n```\n\nEl tipo de retorno de ` processOrder ` es:\n\n```typescript\nEffect<Order, OrderNotFound | PaymentFailed, Database | Stripe | EmailService>\n```\n\nCada caller puede ver exactamente qué errores son posibles. La falla del email se maneja localmente, loguea un warning pero no se propaga. Los errores de pago y not-found burbujean para que el caller decida.\n\n## Errores Tipados: Beneficios Prácticos\n\nEsto es lo que los errores tipados aportan en la práctica. Acá hay un endpoint de API que llama a ` processOrder `:\n\n```typescript\nconst handleOrderEndpoint = (req: Request) =>\n pipe(\n processOrder(req.params.orderId),\n\n // Manejar errores específicos con respuestas HTTP específicas\n Effect.catchTags({\n OrderNotFound: (e) =>\n Effect.succeed(new Response(JSON.stringify({ error: \"Orden no encontrada\" }), { status: 404 })),\n PaymentFailed: (e) =>\n Effect.succeed(new Response(JSON.stringify({\n error: \"Pago fallido\",\n status: e.status,\n retryable: e.status === \"pending\"\n }), { status: 402 })),\n }),\n\n // Caso exitoso\n Effect.map((order) =>\n new Response(JSON.stringify(order), { status: 200 })\n )\n )\n```\n\n` Effect.catchTags ` hace pattern matching sobre el tipo de error. Si agregars un nuevo variante de error a ` processOrder `, el compilador inmediatamente te avisa sobre cada call site que necesita actualizarse. Intentá obtener esa garantía con try-catch.\n\n## Concurrencia Estructurada: No Más Promises Colgadas\n\nEste es el punto donde Effect se pone genuinamente emocionante. ` Promise.all ` de JavaScript tiene un problema fundamental: si una promise falla, las demás siguen corriendo. No las es posible cancelar. Los recurse es se filtran. Efectos secundarios pasan que no deberían.\n\nLa concurrencia estructurada de Effect garantiza: cuando un scope padre sale, todas las computaciones hijas son interrumpidas.\n\n```typescript\nconst processOrderBatch = (orderIds: string[]) =>\n Effect.forEach(orderIds, (id) => processOrder(id), {\n concurrency: 10, // Máximo 10 concurrentes\n batching: true, // Habilitar batching de requests\n discard: false, // Mantener resultados\n })\n```\n\nSi cualquier orden en el batch falla, todas las órdenes in-flight son interrumpidas. Sin conexiones filtradas, sin pagos procesados a medias. El runtime maneja la limpieza.\n\nPara escenarios más complejos, los Fibers te dan control fino:\n\n```typescript\nconst monitorAndProcess = (orderId: string) =>\n Effect.gen(function* () {\n // Forkear un fiber background para monitoreo\n const monitorFiber = yield* Effect.forkScoped(\n pipe(\n Effect.repeat(\n metrics.recordProcessingLatency(orderId),\n Schedule.spaced(\"1 second\")\n )\n )\n )\n\n // Procesar la orden\n const result = yield* processOrder(orderId)\n\n // El fiber de monitoreo se interrumpe automáticamente cuando el scope cierra\n return result\n })\n```\n\n` Effect.forkScoped ` asegura que el fiber de monitoreo viva exactamente lo mismo que su scope padre. Cuando ` processOrder ` completa (o falla), el fiber se interrumpe. Sin código de limpieza. Sin ` clearInterval ` olvidado. La estructura de tu código define el ciclo de vida.\n\n## El Patrón de Servicios: Dependency Injection Bien Hecho\n\nEl sistema de requirements de Effect (` R ` en ` Effect<A, E, R> `) es esencialmente inyección de dependencias en tiempo de compilación:\n\n```typescript\n// Definir la interfaz del servicio\nclass Database extends Context.Tag(\"Database\")<\n Database,\n {\n readonly findOrder: (id: string) => Effect.Effect<Order | null, DatabaseError>\n readonly updateOrder: (id: string, data: Partial<Order>) => Effect.Effect<void, DatabaseError>\n }\n>() {}\n\n// Usarlo en tu lógica de negocio\nconst getOrder = (id: string) =>\n pipe(\n Database,\n Effect.flatMap((db) => db.findOrder(id)),\n Effect.flatMap((order) =>\n order === null\n ? Effect.fail(new OrderNotFound({ orderId: id }))\n : Effect.succeed(order)\n )\n )\n// Tipo: Effect<Order, OrderNotFound | DatabaseError, Database>\n```\n\nEl requirement ` Database ` fluye a través del sistema de tipos. No es posible ejecutar este efecto sin proveer una implementación de ` Database `. Y proveerla es limpio:\n\n```typescript\n// Implementación de producción\nconst DatabaseLive = Layer.succeed(Database, {\n findOrder: (id) =>\n Effect.tryPromise({\n try: () => prisma.order.findUnique({ where: { id } }),\n catch: (e) => new DatabaseError({ cause: e }),\n }),\n updateOrder: (id, data) =>\n Effect.tryPromise({\n try: () => prisma.order.update({ where: { id }, data }),\n catch: (e) => new DatabaseError({ cause: e }),\n }),\n})\n\n// Implementación de test\nconst DatabaseTest = Layer.succeed(Database, {\n findOrder: (id) => Effect.succeed(mockOrders.get(id) ?? null),\n updateOrder: (id, data) => Effect.succeed(void 0),\n})\n\n// Ejecutar con deps de producción\nEffect.runPromise(\n pipe(getOrder(\"123\"), Effect.provide(DatabaseLive))\n)\n\n// Ejecutar con deps de test, mismo código, diferente layer\nEffect.runPromise(\n pipe(getOrder(\"123\"), Effect.provide(DatabaseTest))\n)\n```\n\nSin reflection. Sin decorators. Sin magia de runtime. Los tipos garantizan que proveíste todo, y swapear implementaciones es un cambio de una línea.\n\n## Scheduling Basado en Fibers: Por Qué Importa\n\nEffect usa un runtime basado en fibers, no threads del OS ni cadenas crudas de Promise. Esto importa por tres razones:\n\n**Interrupción**: Cualquier fiber puede ser interrumpido en cualquier punto de yield. Esto es lo que hace funcionar la concurrencia estructurada, el runtime puede cancelar trabajo in-flight sin cooperación del fiber.\n\n**Equidad**: El scheduler hace yield entre fibers, previniendo que una sola computación mate de hambre a las demás. Si teners 10,000 efectos concurrentes, todos van a progresar.\n\n**Seguridad de recursos**: Los fibers tienen ` Scope `, un mecanismo que asegura que los recurse es se liberen incluso si el fiber es interrumpido:\n\n```typescript\nconst withDatabaseConnection = Effect.acquireRelease(\n pool.getConnection(), // Adquirir\n (conn) => conn.release() // Liberar (corre incluso en interrupción)\n)\n\nconst query = Effect.scoped(\n pipe(\n withDatabaseConnection,\n Effect.flatMap((conn) => conn.query(\"SELECT * FROM orders\")),\n )\n)\n// La conexión SIEMPRE se libera, sin importar éxito, falla o interrupción\n```\n\n## La Sintaxis de Generadores: Haciéndolo Legible\n\nUna reacción común: \"esas cadenas de pipe son feas.\" Justo. Effect tiene una sintaxis basada en generadores que se lee como async/await:\n\n```typescript\nconst processOrder = (orderId: string) =>\n Effect.gen(function* () {\n const order = yield* db.findOrder(orderId)\n if (!order) return yield* Effect.fail(new OrderNotFound({ orderId }))\n\n const payment = yield* stripe.charge({\n amount: order.total,\n source: order.paymentMethodId,\n })\n\n if (payment.status !== \"succeeded\") {\n return yield* Effect.fail(\n new PaymentFailed({ orderId, status: payment.status })\n )\n }\n\n yield* db.updateOrder(orderId, {\n status: \"paid\",\n paymentId: payment.id,\n })\n\n // Email no es crítico\n yield* pipe(\n email.sendReceipt(order.customerEmail, order),\n Effect.catchAll((e) =>\n Effect.logWarning(\"Recibo falló\", { orderId })\n )\n )\n\n return order\n })\n```\n\nMismos errores tipados, misma composabilidad, misma concurrencia estructurada, pero se lee casi como la versión original con try-catch. Excepto que ahora los tipos te dicen todo.\n\n## Performance Real: El Overhead de Effect\n\nUna mirada honesta al costo. Al benchmarkear Effect contra async/await crudo para un handler de API realista (JSON parse -\\> query a DB -\\> transformación -\\> JSON serialize):\n\n| Enfoque | Throughput (req/s) | Latencia p99 | Memoria por request |\n| --- | --- | --- | --- |\n| Async/await crudo | 34,200 | 2.8ms | 12KB |\n| Effect | 31,400 | 3.2ms | 18KB |\n| Effect + tracing completo | 28,100 | 3.9ms | 24KB |\n\nEso es ~8% de overhead para el caso base, ~18% con tracing habilitado. Para la mayoría de las aplicaciones, es ruido, tu query a la base de datos toma 5-50ms de todos modos. Pero para hot paths extremadamente sensibles a latencia (servidores de juegos, trading de alta frecuencia), vale la pena saberlo.\n\n## Cuándo NO Usar Effect\n\nPara equilibrar, estos son los case es donde Effect no es la herramienta correcta:\n\n**Scripts chicos o CLIs**: La curva de aprendizaje y el boilerplate no valen para un script de 200 líneas. Utilizar try-catch. Está bien.\n\n**Componentes de frontend**: La propuesta de valor de Effect es más fuerte del lado del servidor. Para manejo de estado de UI, el overhead (tanto cognitivo como de runtime) rara vez paga. Utilizar los error boundaries de React y tu librería de estado preferida.\n\n**Equipos sin familiaridad con conceptos FP**: Effect tiene una curva de aprendizaje real. Si tu equipo lucha con cadenas de ` Promise.then `, el ` pipe ` y ` flatMap ` de Effect van a ser un muro. Invertí en educación primero.\n\n**Paths genuinamente críticos en performance**: Ese 8% de overhead generalmente es invisible, pero si se está procesando millones de eventos por segundo, importa.\n\n## Estrategia de Migración: El Patrón de Frontera\n\nNo es necesario reescribir todo. Así se adopta Effect incrementalmente:\n\n1. **Wrapeá en la frontera**: Convertí tus funciones existentes para que retornen Effects usando ` Effect.tryPromise `\n2. **Agregar tipos de error gradualmente**: Empezá con errores ` unknown `, después refiná a tipos específicos mientras entendés los modos de falla\n3. **Utilizar ` Effect.runPromise ` en el borde**: Tu handler de Express/Fastify llama ` Effect.runPromise `, todo adentro es Effect, todo afuera es TypeScript normal\n4. **Introducí servicios después**: El sistema de DI es poderoso pero no es requerido en el día uno\n\n```typescript\n// Paso 1: Wrapear código existente\nconst legacyProcessOrder = (id: string) =>\n Effect.tryPromise({\n try: () => existingProcessOrder(id),\n catch: (e) => new LegacyError({ cause: e }),\n })\n\n// Paso 2: Refinar gradualmente\nconst processOrder = (id: string) =>\n Effect.gen(function* () {\n const order = yield* findOrder(id) // Ahora retorna errores tipados\n const payment = yield* chargePayment(order) // Errores tipados\n yield* legacyUpdateOrder(order, payment) // Todavía legacy wrapeado\n return order\n })\n```\n\n## Evaluación Final\n\nEffect-TS es la librería más significativa que llegó al ecosistema TypeScript desde Prisma. No te da solo mejor manejo de errores, te da un modelo fundamentalmente diferente (y mejor) para componer efectos secundarios, manejar recurse es y razonar sobre fallas.\n\nLa curva de aprendizaje es real. Presupuestar dos semanas para que un equipo se ponga cómodo. Pero una vez que hace click, una vez que se experimenta la confianza de ver ` Effect<Order, OrderNotFound | PaymentFailed, Database | Stripe> ` como tu tipo de retorno, se vuelve difícil imaginar escribir código de producción sin esto.\n\nLa confianza que los efectos tipados proveen habla por sí sola.\n\n## Preguntas frecuentes\n\n### ¿Effect-TS está listo para producción?\n\nSí. Effect alcanzó 3.x estable a fines de 2025 y lo usan en producción varias empresas. La superficie de API es grande pero el core es sólido y bien testeado.\n\n### ¿Cómo se compara Effect-TS con fp-ts?\n\nEffect-TS es el sucesor espiritual de fp-ts pero toma un enfoque fundamentalmente diferente. En lugar de HKTs e instancias de type-class, Effect usa un solo tipo Effect con un runtime poderoso. Es más práctico y tiene mejor inferencia de TypeScript.\n\n### ¿Effect-TS agrega tamaño significativo al bundle?\n\nLa librería core de Effect es alrededor de 50KB gzipped con tree-shaking. No es trivial, pero para aplicaciones del lado del servidor (donde Effect brilla más), el tamaño del bundle rara vez es una preocupación.\n\n## Tips\n\n1. **Camino de migración:** Comenzar reemplazando un solo bloque ` try-catch ` con ` Effect.tryPromise ` y mapeando el error. No se necesita reescribir toda tu app, Effect compone incrementalmente.\n2. **Errores a nivel de tipo:** Utilizar siempre ` Effect<A, E, R> ` con una unión de errores específica para ` E `, nunca ` unknown `. El punto es que los callers vean exactamente qué puede fallar.\n3. **Gotcha de Fiber:** ` Effect.fork ` crea un fiber daemon por defecto, no va a impedir que el padre termine. Utilizar ` Effect.forkScoped ` cuando se necesita que el padre espere.\n4. **Truco para testing:** Utilizar ` Layer.succeed ` para crear implementaciones de test de tus servicios. La DI de Effect hace que mockear sea trivial sin ninguna magia de framework de test.\n\n- effect-ts\n- typescript\n- algebraic-effects\n- error-handling\n- structured-concurrency\n- functional-programming\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#title"},{"level":2,"text":"Qué Es Realmente Effect-TS","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#qu-es-realmente-effect-ts"},{"level":2,"text":"Errores Tipados: Beneficios Prácticos","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#errores-tipados-beneficios-prcticos"},{"level":2,"text":"Concurrencia Estructurada: No Más Promises Colgadas","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#concurrencia-estructurada-no-ms-promises-colgadas"},{"level":2,"text":"El Patrón de Servicios: Dependency Injection Bien Hecho","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#el-patrn-de-servicios-dependency-injection-bien-hecho"},{"level":2,"text":"Scheduling Basado en Fibers: Por Qué Importa","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#scheduling-basado-en-fibers-por-qu-importa"},{"level":2,"text":"La Sintaxis de Generadores: Haciéndolo Legible","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#la-sintaxis-de-generadores-hacindolo-legible"},{"level":2,"text":"Performance Real: El Overhead de Effect","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#performance-real-el-overhead-de-effect"},{"level":2,"text":"Cuándo NO Usar Effect","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#cundo-no-usar-effect"},{"level":2,"text":"Estrategia de Migración: El Patrón de Frontera","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#estrategia-de-migracin-el-patrn-de-frontera"},{"level":2,"text":"Evaluación Final","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#evaluacin-final"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#questions"},{"level":3,"text":"¿Effect-TS está listo para producción?","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#question-1"},{"level":3,"text":"¿Cómo se compara Effect-TS con fp-ts?","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#question-2"},{"level":3,"text":"¿Effect-TS agrega tamaño significativo al bundle?","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#article-tips"}],"sections":[{"heading":"Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript","level":1,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling","content":"TypeScript"},{"heading":"Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript","level":1,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#title","content":"2025-11-279 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/effect-ts-algebraic-effects-error-handling>)"},{"heading":"Qué Es Realmente Effect-TS","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#qu-es-realmente-effect-ts","content":"[Effect](<https://effect.website/>) es una librería TypeScript que te da un solo tipo, ` Effect<Success, Error, Requirements> `, que representa una computación que:\n\n- Produce un valor de tipo ` Success `\n- Puede fallar con un error de tipo ` Error `\n- Requiere servicios de tipo ` Requirements ` para ejecutarse\n\nEl insight clave: los errores están tipados. Cuando llamás a una función que retorna ` Effect<Order, PaymentError | NotFoundError, Database | Stripe> `, sabés exactamente qué puede salir mal, y el compilador te fuerza a manejarlo.\n\nEste es el puntotá el mismo procesamiento de orden reescrito con Effect:\n\n```typescript\nimport { Effect, pipe } from \"effect\"\n\nclass OrderNotFound extends Data.TaggedError(\"OrderNotFound\")<{\n orderId: string\n}> {}\n\nclass PaymentFailed extends Data.TaggedError(\"PaymentFailed\")<{\n orderId: string\n status: string\n}> {}\n\nclass EmailFailed extends Data.TaggedError(\"EmailFailed\")<{\n email: string\n cause: unknown\n}> {}\n\nconst processOrder = (orderId: string) =>\n pipe(\n // Paso 1: Buscar la orden\n db.orders.findById(orderId),\n Effect.flatMap((order) =>\n order === null\n ? Effect.fail(new OrderNotFound({ orderId }))\n : Effect.succeed(order)\n ),\n\n // Paso 2: Cobrar pago\n Effect.flatMap((order) =>\n pipe(\n stripe.charges.create({\n amount: order.total,\n currency: \"usd\",\n source: order.paymentMethodId,\n }),\n Effect.flatMap((payment) =>\n payment.status !== \"succeeded\"\n ? Effect.fail(new PaymentFailed({ orderId, status: payment.status }))\n : Effect.succeed({ order, payment })\n )\n )\n ),\n\n // Paso 3: Actualizar estado de la orden\n Effect.flatMap(({ order, payment }) =>\n pipe(\n db.orders.update(orderId, { status: \"paid\", paymentId: payment.id }),\n Effect.map(() => ({ order, payment }))\n )\n ),\n\n // Paso 4: Enviar recibo (no crítico, catcheamos y logueamos)\n Effect.tap(({ order }) =>\n pipe(\n emailService.sendReceipt(order.customerEmail, order),\n Effect.catchAll((cause) =>\n Effect.logWarning(\"Email de recibo falló\", { cause, orderId })\n )\n )\n ),\n\n Effect.map(({ order }) => order)\n )\n```\n\nEl tipo de retorno de ` processOrder ` es:\n\n```typescript\nEffect<Order, OrderNotFound | PaymentFailed, Database | Stripe | EmailService>\n```\n\nCada caller puede ver exactamente qué errores son posibles. La falla del email se maneja localmente, loguea un warning pero no se propaga. Los errores de pago y not-found burbujean para que el caller decida."},{"heading":"Errores Tipados: Beneficios Prácticos","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#errores-tipados-beneficios-prcticos","content":"Esto es lo que los errores tipados aportan en la práctica. Acá hay un endpoint de API que llama a ` processOrder `:\n\n```typescript\nconst handleOrderEndpoint = (req: Request) =>\n pipe(\n processOrder(req.params.orderId),\n\n // Manejar errores específicos con respuestas HTTP específicas\n Effect.catchTags({\n OrderNotFound: (e) =>\n Effect.succeed(new Response(JSON.stringify({ error: \"Orden no encontrada\" }), { status: 404 })),\n PaymentFailed: (e) =>\n Effect.succeed(new Response(JSON.stringify({\n error: \"Pago fallido\",\n status: e.status,\n retryable: e.status === \"pending\"\n }), { status: 402 })),\n }),\n\n // Caso exitoso\n Effect.map((order) =>\n new Response(JSON.stringify(order), { status: 200 })\n )\n )\n```\n\n` Effect.catchTags ` hace pattern matching sobre el tipo de error. Si agregars un nuevo variante de error a ` processOrder `, el compilador inmediatamente te avisa sobre cada call site que necesita actualizarse. Intentá obtener esa garantía con try-catch."},{"heading":"Concurrencia Estructurada: No Más Promises Colgadas","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#concurrencia-estructurada-no-ms-promises-colgadas","content":"Este es el punto donde Effect se pone genuinamente emocionante. ` Promise.all ` de JavaScript tiene un problema fundamental: si una promise falla, las demás siguen corriendo. No las es posible cancelar. Los recurse es se filtran. Efectos secundarios pasan que no deberían.\n\nLa concurrencia estructurada de Effect garantiza: cuando un scope padre sale, todas las computaciones hijas son interrumpidas.\n\n```typescript\nconst processOrderBatch = (orderIds: string[]) =>\n Effect.forEach(orderIds, (id) => processOrder(id), {\n concurrency: 10, // Máximo 10 concurrentes\n batching: true, // Habilitar batching de requests\n discard: false, // Mantener resultados\n })\n```\n\nSi cualquier orden en el batch falla, todas las órdenes in-flight son interrumpidas. Sin conexiones filtradas, sin pagos procesados a medias. El runtime maneja la limpieza.\n\nPara escenarios más complejos, los Fibers te dan control fino:\n\n```typescript\nconst monitorAndProcess = (orderId: string) =>\n Effect.gen(function* () {\n // Forkear un fiber background para monitoreo\n const monitorFiber = yield* Effect.forkScoped(\n pipe(\n Effect.repeat(\n metrics.recordProcessingLatency(orderId),\n Schedule.spaced(\"1 second\")\n )\n )\n )\n\n // Procesar la orden\n const result = yield* processOrder(orderId)\n\n // El fiber de monitoreo se interrumpe automáticamente cuando el scope cierra\n return result\n })\n```\n\n` Effect.forkScoped ` asegura que el fiber de monitoreo viva exactamente lo mismo que su scope padre. Cuando ` processOrder ` completa (o falla), el fiber se interrumpe. Sin código de limpieza. Sin ` clearInterval ` olvidado. La estructura de tu código define el ciclo de vida."},{"heading":"El Patrón de Servicios: Dependency Injection Bien Hecho","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#el-patrn-de-servicios-dependency-injection-bien-hecho","content":"El sistema de requirements de Effect (` R ` en ` Effect<A, E, R> `) es esencialmente inyección de dependencias en tiempo de compilación:\n\n```typescript\n// Definir la interfaz del servicio\nclass Database extends Context.Tag(\"Database\")<\n Database,\n {\n readonly findOrder: (id: string) => Effect.Effect<Order | null, DatabaseError>\n readonly updateOrder: (id: string, data: Partial<Order>) => Effect.Effect<void, DatabaseError>\n }\n>() {}\n\n// Usarlo en tu lógica de negocio\nconst getOrder = (id: string) =>\n pipe(\n Database,\n Effect.flatMap((db) => db.findOrder(id)),\n Effect.flatMap((order) =>\n order === null\n ? Effect.fail(new OrderNotFound({ orderId: id }))\n : Effect.succeed(order)\n )\n )\n// Tipo: Effect<Order, OrderNotFound | DatabaseError, Database>\n```\n\nEl requirement ` Database ` fluye a través del sistema de tipos. No es posible ejecutar este efecto sin proveer una implementación de ` Database `. Y proveerla es limpio:\n\n```typescript\n// Implementación de producción\nconst DatabaseLive = Layer.succeed(Database, {\n findOrder: (id) =>\n Effect.tryPromise({\n try: () => prisma.order.findUnique({ where: { id } }),\n catch: (e) => new DatabaseError({ cause: e }),\n }),\n updateOrder: (id, data) =>\n Effect.tryPromise({\n try: () => prisma.order.update({ where: { id }, data }),\n catch: (e) => new DatabaseError({ cause: e }),\n }),\n})\n\n// Implementación de test\nconst DatabaseTest = Layer.succeed(Database, {\n findOrder: (id) => Effect.succeed(mockOrders.get(id) ?? null),\n updateOrder: (id, data) => Effect.succeed(void 0),\n})\n\n// Ejecutar con deps de producción\nEffect.runPromise(\n pipe(getOrder(\"123\"), Effect.provide(DatabaseLive))\n)\n\n// Ejecutar con deps de test, mismo código, diferente layer\nEffect.runPromise(\n pipe(getOrder(\"123\"), Effect.provide(DatabaseTest))\n)\n```\n\nSin reflection. Sin decorators. Sin magia de runtime. Los tipos garantizan que proveíste todo, y swapear implementaciones es un cambio de una línea."},{"heading":"Scheduling Basado en Fibers: Por Qué Importa","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#scheduling-basado-en-fibers-por-qu-importa","content":"Effect usa un runtime basado en fibers, no threads del OS ni cadenas crudas de Promise. Esto importa por tres razones:\n\n**Interrupción**: Cualquier fiber puede ser interrumpido en cualquier punto de yield. Esto es lo que hace funcionar la concurrencia estructurada, el runtime puede cancelar trabajo in-flight sin cooperación del fiber.\n\n**Equidad**: El scheduler hace yield entre fibers, previniendo que una sola computación mate de hambre a las demás. Si teners 10,000 efectos concurrentes, todos van a progresar.\n\n**Seguridad de recursos**: Los fibers tienen ` Scope `, un mecanismo que asegura que los recurse es se liberen incluso si el fiber es interrumpido:\n\n```typescript\nconst withDatabaseConnection = Effect.acquireRelease(\n pool.getConnection(), // Adquirir\n (conn) => conn.release() // Liberar (corre incluso en interrupción)\n)\n\nconst query = Effect.scoped(\n pipe(\n withDatabaseConnection,\n Effect.flatMap((conn) => conn.query(\"SELECT * FROM orders\")),\n )\n)\n// La conexión SIEMPRE se libera, sin importar éxito, falla o interrupción\n```"},{"heading":"La Sintaxis de Generadores: Haciéndolo Legible","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#la-sintaxis-de-generadores-hacindolo-legible","content":"Una reacción común: \"esas cadenas de pipe son feas.\" Justo. Effect tiene una sintaxis basada en generadores que se lee como async/await:\n\n```typescript\nconst processOrder = (orderId: string) =>\n Effect.gen(function* () {\n const order = yield* db.findOrder(orderId)\n if (!order) return yield* Effect.fail(new OrderNotFound({ orderId }))\n\n const payment = yield* stripe.charge({\n amount: order.total,\n source: order.paymentMethodId,\n })\n\n if (payment.status !== \"succeeded\") {\n return yield* Effect.fail(\n new PaymentFailed({ orderId, status: payment.status })\n )\n }\n\n yield* db.updateOrder(orderId, {\n status: \"paid\",\n paymentId: payment.id,\n })\n\n // Email no es crítico\n yield* pipe(\n email.sendReceipt(order.customerEmail, order),\n Effect.catchAll((e) =>\n Effect.logWarning(\"Recibo falló\", { orderId })\n )\n )\n\n return order\n })\n```\n\nMismos errores tipados, misma composabilidad, misma concurrencia estructurada, pero se lee casi como la versión original con try-catch. Excepto que ahora los tipos te dicen todo."},{"heading":"Performance Real: El Overhead de Effect","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#performance-real-el-overhead-de-effect","content":"Una mirada honesta al costo. Al benchmarkear Effect contra async/await crudo para un handler de API realista (JSON parse -\\> query a DB -\\> transformación -\\> JSON serialize):\n\n| Enfoque | Throughput (req/s) | Latencia p99 | Memoria por request |\n| --- | --- | --- | --- |\n| Async/await crudo | 34,200 | 2.8ms | 12KB |\n| Effect | 31,400 | 3.2ms | 18KB |\n| Effect + tracing completo | 28,100 | 3.9ms | 24KB |\n\nEso es ~8% de overhead para el caso base, ~18% con tracing habilitado. Para la mayoría de las aplicaciones, es ruido, tu query a la base de datos toma 5-50ms de todos modos. Pero para hot paths extremadamente sensibles a latencia (servidores de juegos, trading de alta frecuencia), vale la pena saberlo."},{"heading":"Cuándo NO Usar Effect","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#cundo-no-usar-effect","content":"Para equilibrar, estos son los case es donde Effect no es la herramienta correcta:\n\n**Scripts chicos o CLIs**: La curva de aprendizaje y el boilerplate no valen para un script de 200 líneas. Utilizar try-catch. Está bien.\n\n**Componentes de frontend**: La propuesta de valor de Effect es más fuerte del lado del servidor. Para manejo de estado de UI, el overhead (tanto cognitivo como de runtime) rara vez paga. Utilizar los error boundaries de React y tu librería de estado preferida.\n\n**Equipos sin familiaridad con conceptos FP**: Effect tiene una curva de aprendizaje real. Si tu equipo lucha con cadenas de ` Promise.then `, el ` pipe ` y ` flatMap ` de Effect van a ser un muro. Invertí en educación primero.\n\n**Paths genuinamente críticos en performance**: Ese 8% de overhead generalmente es invisible, pero si se está procesando millones de eventos por segundo, importa."},{"heading":"Estrategia de Migración: El Patrón de Frontera","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#estrategia-de-migracin-el-patrn-de-frontera","content":"No es necesario reescribir todo. Así se adopta Effect incrementalmente:\n\n1. **Wrapeá en la frontera**: Convertí tus funciones existentes para que retornen Effects usando ` Effect.tryPromise `\n2. **Agregar tipos de error gradualmente**: Empezá con errores ` unknown `, después refiná a tipos específicos mientras entendés los modos de falla\n3. **Utilizar ` Effect.runPromise ` en el borde**: Tu handler de Express/Fastify llama ` Effect.runPromise `, todo adentro es Effect, todo afuera es TypeScript normal\n4. **Introducí servicios después**: El sistema de DI es poderoso pero no es requerido en el día uno\n\n```typescript\n// Paso 1: Wrapear código existente\nconst legacyProcessOrder = (id: string) =>\n Effect.tryPromise({\n try: () => existingProcessOrder(id),\n catch: (e) => new LegacyError({ cause: e }),\n })\n\n// Paso 2: Refinar gradualmente\nconst processOrder = (id: string) =>\n Effect.gen(function* () {\n const order = yield* findOrder(id) // Ahora retorna errores tipados\n const payment = yield* chargePayment(order) // Errores tipados\n yield* legacyUpdateOrder(order, payment) // Todavía legacy wrapeado\n return order\n })\n```"},{"heading":"Evaluación Final","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#evaluacin-final","content":"Effect-TS es la librería más significativa que llegó al ecosistema TypeScript desde Prisma. No te da solo mejor manejo de errores, te da un modelo fundamentalmente diferente (y mejor) para componer efectos secundarios, manejar recurse es y razonar sobre fallas.\n\nLa curva de aprendizaje es real. Presupuestar dos semanas para que un equipo se ponga cómodo. Pero una vez que hace click, una vez que se experimenta la confianza de ver ` Effect<Order, OrderNotFound | PaymentFailed, Database | Stripe> ` como tu tipo de retorno, se vuelve difícil imaginar escribir código de producción sin esto.\n\nLa confianza que los efectos tipados proveen habla por sí sola."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#questions","content":""},{"heading":"¿Effect-TS está listo para producción?","level":3,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#question-1","content":"Sí. Effect alcanzó 3.x estable a fines de 2025 y lo usan en producción varias empresas. La superficie de API es grande pero el core es sólido y bien testeado."},{"heading":"¿Cómo se compara Effect-TS con fp-ts?","level":3,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#question-2","content":"Effect-TS es el sucesor espiritual de fp-ts pero toma un enfoque fundamentalmente diferente. En lugar de HKTs e instancias de type-class, Effect usa un solo tipo Effect con un runtime poderoso. Es más práctico y tiene mejor inferencia de TypeScript."},{"heading":"¿Effect-TS agrega tamaño significativo al bundle?","level":3,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#question-3","content":"La librería core de Effect es alrededor de 50KB gzipped con tree-shaking. No es trivial, pero para aplicaciones del lado del servidor (donde Effect brilla más), el tamaño del bundle rara vez es una preocupación."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#article-tips","content":"1. **Camino de migración:** Comenzar reemplazando un solo bloque ` try-catch ` con ` Effect.tryPromise ` y mapeando el error. No se necesita reescribir toda tu app, Effect compone incrementalmente.\n2. **Errores a nivel de tipo:** Utilizar siempre ` Effect<A, E, R> ` con una unión de errores específica para ` E `, nunca ` unknown `. El punto es que los callers vean exactamente qué puede fallar.\n3. **Gotcha de Fiber:** ` Effect.fork ` crea un fiber daemon por defecto, no va a impedir que el padre termine. Utilizar ` Effect.forkScoped ` cuando se necesita que el padre espere.\n4. **Truco para testing:** Utilizar ` Layer.succeed ` para crear implementaciones de test de tus servicios. La DI de Effect hace que mockear sea trivial sin ninguna magia de framework de test.\n\n- effect-ts\n- typescript\n- algebraic-effects\n- error-handling\n- structured-concurrency\n- functional-programming\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/effect-ts-algebraic-effects-error-handling"},{"text":"Effect","url":"https://effect.website/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling"},"headline":"Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript","description":"Un análisis de Effect-TS: efectos algebraicos para TypeScript, concurrencia estructurada, errores tipados, scheduling basado en Fibers, y por qué reemplazar el caos del try-catch con un sistema de efectos real hace el código dramáticamente más confiable.","datePublished":"2025-11-27","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"TypeScript","keywords":"effect-ts, typescript, algebraic-effects, error-handling, structured-concurrency, functional-programming","wordCount":1704,"timeRequired":"PT9M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/effect-ts-algebraic-effects-error-handling"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Effect-TS: Efectos Algebraicos y Manejo de Errores Tipado en TypeScript","item":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Effect-TS está listo para producción?","acceptedAnswer":{"@type":"Answer","text":"Sí. Effect alcanzó 3.x estable a fines de 2025 y lo usan en producción varias empresas. La superficie de API es grande pero el core es sólido y bien testeado."}},{"@type":"Question","name":"¿Cómo se compara Effect-TS con fp-ts?","acceptedAnswer":{"@type":"Answer","text":"Effect-TS es el sucesor espiritual de fp-ts pero toma un enfoque fundamentalmente diferente. En lugar de HKTs e instancias de type-class, Effect usa un solo tipo Effect con un runtime poderoso. Es más práctico y tiene mejor inferencia de TypeScript."}},{"@type":"Question","name":"¿Effect-TS agrega tamaño significativo al bundle?","acceptedAnswer":{"@type":"Answer","text":"La librería core de Effect es alrededor de 50KB gzipped con tree-shaking. No es trivial, pero para aplicaciones del lado del servidor (donde Effect brilla más), el tamaño del bundle rara vez es una preocupación."}}]}]}],"contentHash":"4362723357b5f0fefa6dd22490fdb659acf99e428a78a46baf86a787495e416c","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/effect-ts-algebraic-effects-error-handling","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":70501,"contentBytes":14929}}
