[{"content":"En el artículo anterior vimos cómo las Skills nos permiten que un agente revise el trabajo de otro, reduciendo el cuello de botella del ingeniero. Pero el análisis acabó con una conclusión incómoda: la skill detectó 9 issues, dos de ellos críticos — datos de dominio perdidos en runtime — y no fueron errores del agente. El agente implementó exactamente lo que se le pidió. El problema estaba en los requisitos: se describió el qué y el cómo, pero nadie pensó en el impacto sobre el resto del sistema.\nEl problema de los requisitos ambiguos no es nuevo — lleva con nosotros desde que existe el software. Lo que cambia con la IA es la velocidad a la que se acumula el daño: si un agente puede generar cientos de líneas de código por minuto partiendo de una instrucción incompleta, el error escala al mismo ritmo. Eso nos lleva al Spec Driven Development, la metodología que exploraremos en este artículo.\nEl problema real con los requisitos # En el mundo pre-IA y en el actual siempre hay un factor común y es que los requisitos no estan claro en el inicio del proyecto, pero las prisas de empezarlo, de avanzar, de tenerlo en la fecha acordada pues hace vamos tirando con como creemos que es lo más probable que quiera y si luego no es así ya hacemos los \u0026ldquo;ajustes\u0026rdquo; cuando este claro. Igual esos ajustes impactan en un cambio total de la aplicación y estamos en el mismo punto de partida del inicio. Pero era algo cultural que se aceptaba.\nRepasemos el ejemplo anterior cuando le pedimos a un agente \u0026ldquo;crea el endpoint POST /characters para añadir un personaje\u0026rdquo;, le estamos dando una instrucción incompleta. Igual que ocurriría con un ingeniero nuevo: implementa lo que entiende, y lo que entiende es solo lo que le has dicho y no lo que tu piensas que el va a entender.\nEl problema con ImageUrl del artículo anterior es un ejemplo perfecto. El modelo Character tenía ese campo porque queríamos mostrar la imagen en el frontend. Pero en el requisito de \u0026ldquo;crear personaje\u0026rdquo; nadie lo mencionó. El agente no tenía forma de saber que ese campo era obligatorio en el dominio — no estaba en los requisitos, no estaba en las reglas del CLAUDE.md ni debia de estar, y tampoco saltaba ningún constraint de base de datos que pudiera detectar. Simplemente lo ignoró.\nEste tipo de error no se resuelve con más contexto en el CLAUDE.md ni con una skill de revisión más exhaustiva. Se resuelve antes: en el momento de definir el requisito.\nQué es el Spec Driven Development # El Spec Driven Development (SDD) no es un concepto nuevo. La idea de escribir las especificaciones antes que el código existe desde hace tiempo: es la filosofía detrás de cosas como API-first design u OpenAPI. Lo que cambia con la IA es la importancia que cobra.\nCuando programas tú mismo, puedes compensar un requisito mal definido con tu conocimiento del sistema. Sabes que ImageUrl es obligatorio porque llevas meses en el proyecto. El agente no tiene ese conocimiento implícito. Si no está escrito, no existe.\nEl SDD en este contexto significa: antes de pedirle al agente que implemente nada, escribir una especificación que sea completa, sin ambigüedades y verificable. Una especificación que responda no solo al qué, sino también al impacto en el dominio, a las reglas de negocio y a los criterios de aceptación.\nLa diferencia entre un requisito y una spec:\nRequisito Spec Consecuencia de no tenerla \u0026ldquo;Crea el endpoint POST /characters\u0026rdquo; Define el contrato completo: campos obligatorios, validaciones, respuesta esperada, impacto en el dominio El agente genera el endpoint sin ImageUrl porque nadie dijo que era obligatorio en el dominio. Compila, los tests básicos pasan, llega a producción con datos corruptos. \u0026ldquo;Añade paginación a la lista\u0026rdquo; Define el comportamiento exacto: parámetros, valores por defecto, límites, formato de respuesta, qué ocurre en página fuera de rango El agente inventa los nombres de los parámetros (page/limit, offset/size, pageIndex/pageSize\u0026hellip;) y el frontend que ya consumía el endpoint se rompe silenciosamente. \u0026ldquo;Muestra el detalle del personaje\u0026rdquo; Define qué datos se exponen, qué pasa si el ID no existe, qué relaciones se cargan El agente devuelve 200 con body vacío o null en lugar de 404. El frontend no sabe distinguir \u0026ldquo;personaje sin datos\u0026rdquo; de \u0026ldquo;personaje inexistente\u0026rdquo; y muestra una pantalla en blanco. \u0026ldquo;El usuario puede editar el personaje\u0026rdquo; Define qué campos son editables, si es PUT completo o PATCH parcial, qué pasa si se envían campos de solo lectura, quién puede editar El agente implementa PUT que sobreescribe todos los campos. El primer usuario que edita el nombre borra la imagen porque no la incluyó en el payload. Cómo escribir una spec que funcione con agentes # Una spec útil para un agente tiene que cubrir cuatro cosas:\n1. El contrato de la API\nQué endpoint, qué método HTTP, qué body recibe, qué responde en cada caso. Cuanto más cerca del formato OpenAPI, mejor — los agentes lo entienden bien y puedes usarlo también para generar documentación.\n2. Las reglas del dominio\nQué invariantes tiene que respetar la operación. Qué campos son obligatorios en el modelo de dominio (no solo en el request). Qué restricciones de negocio aplican.\n3. Los criterios de aceptación\nQué tests tienen que pasar para considerar la feature completa. Escritos en lenguaje de comportamiento: dado X, cuando Y, entonces Z. Esto es lo que la skill de revisión puede verificar.\n4. El impacto en el sistema\nQué otras partes del código se ven afectadas. Si añadir un personaje requiere que el frontend lo pueda mostrar con imagen, eso tiene que estar en la spec — no es responsabilidad del agente inferirlo.\nLa spec del endpoint CreateCharacter # Volvamos al ejemplo de la API de DragonBall. Este es el requisito original que causó los 9 issues:\n\u0026ldquo;Añade el endpoint POST /characters para crear un personaje\u0026rdquo;\nY esta es la spec equivalente:\n## Feature: Crear personaje ### Contrato de API POST /characters Request body: - name (string, obligatorio, max 100 chars) - race (string, obligatorio, max 50 chars) - powerLevel (int, obligatorio, \u0026gt; 0, usar long para evitar overflow con valores altos) - affiliation (string, obligatorio, max 100 chars) - imageUrl (string, obligatorio, URL válida — requerido por el dominio para mostrar imagen en el frontend) Respuestas: - 201 Created: devuelve el Character creado con id generado - 422 Unprocessable Entity: si algún campo no cumple la validación (usar FluentValidation + WithValidation()) ### Reglas de dominio - Character es una entidad con los campos: Id, Name, Race, PowerLevel, Affiliation, ImageUrl - ImageUrl es obligatorio en el modelo de dominio — un personaje sin imagen no es válido - PowerLevel debe ser long, no int — valores como 1.400.000.000 rozan el límite de int ### Impacto en el sistema - El frontend (CharacterPage.tsx) muestra la imagen del personaje usando ImageUrl - El repositorio InMemoryCharacterRepository.Add debe copiar todos los campos del Command, incluido ImageUrl - Añadir el campo ImageUrl al CreateCharacterCommand, CreateCharacterRequest y CreateCharacterValidator ### Criterios de aceptación - [ ] POST /characters con body válido devuelve 201 y el personaje creado con id - [ ] POST /characters con name vacío devuelve 422 - [ ] POST /characters con powerLevel = 0 devuelve 422 - [ ] POST /characters sin imageUrl devuelve 422 - [ ] POST /characters con imageUrl inválida devuelve 422 - [ ] El personaje creado es visible en GET /characters con su imagen - [ ] Los tests pasan CancellationToken en todas las llamadas asíncronas Esta spec tiene treinta líneas. Con ella, ninguno de los 9 issues del artículo anterior habría llegado a la skill de revisión.\nEl agente con la spec: resultado # Le pasamos esta spec a Claude con el mismo prompt de antes, pero ahora con la especificación completa como contexto:\n\u0026ldquo;Implementa esta feature siguiendo la spec y las guidelines del proyecto\u0026rdquo;\nEl resultado es radicalmente diferente. El agente genera CreateCharacterCommand incluyendo ImageUrl:\npublic sealed record CreateCharacterCommand( string Name, string Race, long PowerLevel, string Affiliation, string ImageUrl); El validador cubre todos los campos obligatorios, incluyendo la URL:\npublic sealed class CreateCharacterValidator : AbstractValidator\u0026lt;CreateCharacterCommand\u0026gt; { public CreateCharacterValidator() { RuleFor(x =\u0026gt; x.Name) .NotEmpty().WithMessage(\u0026#34;El nombre es obligatorio.\u0026#34;) .MaximumLength(100); RuleFor(x =\u0026gt; x.Race) .NotEmpty().WithMessage(\u0026#34;La raza es obligatoria.\u0026#34;) .MaximumLength(50); RuleFor(x =\u0026gt; x.PowerLevel) .GreaterThan(0L).WithMessage(\u0026#34;El nivel de poder debe ser mayor que 0.\u0026#34;); RuleFor(x =\u0026gt; x.Affiliation) .NotEmpty().WithMessage(\u0026#34;La afiliación es obligatoria.\u0026#34;) .MaximumLength(100); RuleFor(x =\u0026gt; x.ImageUrl) .NotEmpty().WithMessage(\u0026#34;La URL de imagen es obligatoria.\u0026#34;) .Must(url =\u0026gt; Uri.TryCreate(url, UriKind.Absolute, out _)) .WithMessage(\u0026#34;La URL de imagen no es válida.\u0026#34;); } } El repositorio copia todos los campos correctamente:\npublic Character Add(CreateCharacterCommand command) { var character = new Character { Id = _characters.Max(c =\u0026gt; c.Id) + 1, Name = command.Name, Race = command.Race, PowerLevel = command.PowerLevel, Affiliation = command.Affiliation, ImageUrl = command.ImageUrl }; _characters.Add(character); return character; } Y los tests cubren los criterios de aceptación de la spec, incluyendo el nuevo campo:\n[Fact] public async Task POST_characters_without_image_url_returns_422() { var body = new { Name = \u0026#34;Bardock\u0026#34;, Race = \u0026#34;Saiyan\u0026#34;, PowerLevel = 10000L, Affiliation = \u0026#34;None\u0026#34;, ImageUrl = \u0026#34;\u0026#34; }; var response = await _client.PostAsJsonAsync(\u0026#34;/characters\u0026#34;, body, TestContext.Current.CancellationToken); response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); } La skill de revisión pasa sin issues críticos. El mismo agente, el mismo prompt base, pero con una spec que elimina la ambigüedad.\nDónde vive la spec # Una spec que no está versionada junto al código no sirve de nada. Si vive en una wiki, en un Notion o en un email, ya está desincronizada en cuanto el código cambia.\nLa forma de hacerlo es sencilla: la spec vive en el repositorio, en una carpeta docs/specs/ o directamente junto a la feature si es específica:\ndocs/ └── specs/ ├── create-character.md ├── get-character-by-id.md └── update-character.md De esta forma:\nLa spec pasa por revisión en la PR igual que el código El agente puede leerla directamente cuando le pides que implemente La skill de revisión puede validar el código contra ella Si la spec cambia, el diff de git lo muestra junto al cambio de código Esta es la parte que más cuesta adoptar en equipos que llevan tiempo sin hacerlo, porque requiere escribir antes de implementar. Pero es también la que más valor aporta: si no defines el comportamiento esperado, no puedes verificar que lo que el agente genera es correcto.\nDicho esto, hay una fricción real que sería deshonesto ignorar: cuando los requisitos cambian a mitad del desarrollo — y cambian, siempre — actualizar la spec es un paso extra que bajo presión se suele saltar. Una spec desactualizada es casi peor que no tenerla, porque el agente genera código confiando en información incorrecta y los tests validan un comportamiento que ya no es el acordado. La única forma de evitarlo es tratar la spec con la misma disciplina que el código: si un requisito cambia, la PR que introduce ese cambio también actualiza la spec. Si eso no ocurre, el equipo está acumulando deuda de contexto igual que acumula deuda técnica.\nDe la spec monolítica a la entrega iterativa: spec-kit # Todo lo anterior — qué es el SDD, cómo estructurar una spec, dónde versionarla — describe la filosofía: el qué y el por qué. Lo que viene ahora es el cómo llevarlo a escala cuando tienes múltiples features, múltiples agentes y necesitas que el progreso sea visible sin que alguien lo persiga a mano.\nLa respuesta a ese problema no es abandonar la spec — es hacer que se ejecute.\nAquí está el salto conceptual que diferencia este enfoque de cualquier otro artículo sobre SDD: no hablamos de una spec como documento que alguien escribe y los demás leen. Hablamos de una spec que es el programa de trabajo del equipo. spec-kit materializa exactamente eso: en lugar de que la spec guíe la implementación, la genera directamente a través de un agente. El flujo no es \u0026ldquo;escribe la spec, luego codifica\u0026rdquo; — es \u0026ldquo;escribe la spec, el agente la convierte en tareas ordenadas, las ejecuta respetando dependencias y marca el progreso en tiempo real\u0026rdquo;.\nLa consecuencia práctica es que la entrega incremental deja de ser algo que el equipo tiene que planificar a mano. spec-kit genera el desglose de tareas con dependencias explícitas, marcadores de paralelismo y checkpoints verificables por historia de usuario, de forma que el agente sabe exactamente qué puede hacer ahora, qué tiene que esperar y cuándo una historia está lista para entregar. La spec deja de ser un artefacto de análisis y pasa a ser el único punto de verdad que conecta el requisito de negocio con la tarea que ejecuta el agente.\nFunciona con Claude Code, GitHub Copilot, Gemini CLI y más de 30 agentes.\nInstalación # Un detalle de nomenclatura antes de continuar: el proyecto se llama spec-kit en GitHub, el paquete que se instala es specify-cli y el comando que usarás en el terminal es specify. Es la misma herramienta, con tres nombres según el contexto.\nuv tool install specify-cli --from git+https://github.com/github/spec-kit.git@v0.10.2 specify init my-project --integration claude-code cd my-project Esto crea la estructura .specify/ con plantillas, scripts y la configuración de integración con tu agente.\nEl flujo de trabajo # spec-kit propone un pipeline de comandos que se ejecutan dentro del agente. Cada uno genera un artefacto que sirve de entrada al siguiente. Lo hemos aplicado directamente en el repositorio de DragonBall — todos los ejemplos que siguen son reales.\n1. Constitución del proyecto\n/speckit.constitution Genera .specify/memory/constitution.md — las reglas que el agente respetará en todos los pasos. En el proyecto de DragonBall la constitución define, entre otras cosas:\n### III. Validaciones de Dominio - FluentValidation es el único mecanismo para validar comandos y queries. - Los errores de validación retornan HTTP 422 usando Results.ValidationProblem(...) - Las entidades de dominio en src/Domain DEBEN proteger sus invariantes en el constructor ### V. Testing Disciplinado - Una prueba unitaria por cada regla de negocio crítica. No más, no menos. - PROHIBIDO usar mocks complejos (\u0026gt; 2 niveles de setup). Preferir stubs simples. - Todo método async DEBE aceptar CancellationToken. Usar TestContext.Current.CancellationToken en tests. ## Definition of Done 1. dotnet build y npm run build sin errores ni warnings 2. dotnet test y npm test pasan al 100% 3. Sin código muerto: no hay variables, imports ni métodos sin uso 4. Los errores de dominio retornan 422 con descripción clara 5. Los nuevos endpoints aparecen en /scalar/v1 con tipos correctos Esto es lo que el agente lee antes de generar cualquier línea de código. En el artículo anterior, la constitución habría sido la pieza que documenta que ImageUrl es obligatorio en el dominio.\n2. Escribir la spec (el qué y el por qué, sin entrar en el cómo)\n/speckit.specify Necesitamos un CRUD completo de personajes de DragonBall. El usuario puede ver la lista, consultar el detalle, crear nuevos personajes, actualizar los existentes de forma parcial y eliminarlos. Los personajes tienen nombre, apellido (obligatorio), raza (opcional), nivel de poder (\u0026gt;= 0), descripción (opcional) e imagen. El artefacto que genera spec-kit en specs/001-characters-crud/spec.md tiene este aspecto:\n# Feature Specification: Dragon Ball Characters — CRUD API ## User Stories \u0026amp; Acceptance Criteria ### User Story 1: Consultar el catálogo (P1) - GET /characters devuelve HTTP 200 con array JSON de al menos 5 personajes - GET /characters/{id} con ID válido devuelve HTTP 200 con el personaje - GET /characters/{id} con ID inexistente devuelve HTTP 404 con mensaje descriptivo ### User Story 2: Añadir nuevo personaje (P2) - Payload válido con name, lastName y powerLevel (\u0026gt;= 0) devuelve HTTP 201 con ID generado - name vacío o ausente → HTTP 422 - lastName vacío o ausente → HTTP 422 - powerLevel negativo → HTTP 422 - powerLevel = 0 está permitido → HTTP 201 ### User Story 3: Modificar personaje existente (P2) - PUT /characters/{id} con payload parcial → HTTP 200, solo se actualizan los campos enviados - ID inexistente → HTTP 404 - Payload vacío {} → HTTP 200 sin cambios (no-op válido) ### User Story 4: Eliminar personaje (P3) - DELETE válido → HTTP 204 - ID inexistente → HTTP 404 - GET posterior al DELETE → HTTP 404 ## Reglas de validación - Name: obligatorio, no vacío, no solo espacios, máx 100 caracteres - LastName: obligatorio, no vacío, no solo espacios, máx 100 caracteres - Race: opcional, máx 100 caracteres - PowerLevel: obligatorio, entero no negativo - Description: opcional, máx 500 caracteres ## Restricciones técnicas - Sin base de datos — almacenamiento en memoria como Singleton - Sin autenticación ni autorización - CORS configurado para http://localhost:5173 - Scalar en /scalar/v1 Fíjate en dos cosas. Primera: el agente documenta explícitamente que powerLevel = 0 es válido — exactamente la ambigüedad que habría quedado sin resolver en el flujo anterior. Segunda: lastName aparece como campo obligatorio en el contrato desde el primer artefacto, no como corrección posterior. Este fichero es el que alimenta /speckit.clarify y /speckit.plan en los pasos siguientes.\n3. Clarificación (antes de planificar)\n/speckit.clarify El agente formula preguntas estructuradas sobre lo que está ambiguo antes de crear el plan técnico. En el proyecto de DragonBall habría preguntado, por ejemplo: ¿powerLevel: 0 es un valor de negocio válido o debe ser mayor que cero? La respuesta — que sí es válido — quedó recogida en la spec y se reflejó después en el validator (GreaterThanOrEqualTo(0) en lugar del GreaterThan(0) que el agente habría asumido por defecto).\n4. Plan técnico\n/speckit.plan La API usa .NET 10 con Minimal APIs y MinApiLib. El frontend es React 19 con Vite y TypeScript estricto. Base de datos en memoria para el ejemplo. Genera specs/001-characters-crud/plan.md, data-model.md y api-spec.json. El plan descompone el trabajo en bloques: Domain Foundation → Application → API Endpoints → Frontend → Tests. Cada bloque declara sus dependencias explícitamente.\n5. Desglose en tareas\n/speckit.tasks Genera specs/001-characters-crud/tasks.md — el artefacto más valioso para la entrega iterativa. Las tareas se organizan en fases, con marcadores [P] para las que pueden ejecutarse en paralelo y checkpoints explícitos entre fases.\nEste es el desglose real del proyecto:\n## Phase 1: Setup — Verificación del estado inicial - [ ] T001 Ejecutar dotnet build y confirmar cero errores - [ ] T002 Ejecutar dotnet test (línea base: 31 tests en verde) - [ ] T003 Ejecutar npm test en src/frontend/ (línea base: 24 tests) - [ ] T004 Verificar que GET /characters devuelve 200 --- ## Phase 2: Foundational — Capa de Dominio (bloqueante) ⚠️ CRÍTICO: Completar T005–T008 antes de avanzar al Phase 3. - [ ] T005 Modificar Character.cs: añadir LastName (obligatorio), Description (opcional); hacer Race, Affiliation, ImageUrl nullable - [ ] T006 Modificar ICharacterRepository.cs: añadir Update() y Delete() - [ ] T007 Implementar Update() con merge parcial y Delete() en InMemoryCharacterRepository - [ ] T008 Añadir LastName al dataset inicial de 40 personajes Checkpoint: dotnet build DEBE compilar sin errores antes de continuar. --- ## Phase 3: User Story 1 — Consultar el catálogo (MVP) 🎯 - [ ] T009 [US1] Mover MapOpenApi() y MapScalarApiReference() fuera del bloque IsDevelopment - [ ] T010 [US1] [P] Añadir WithName, WithTags, Produces a GetCharactersEndpoint - [ ] T011 [US1] [P] Añadir WithName, WithTags, Produces a GetCharacterByIdEndpoint - [ ] T014 [US1] Tests de integración: respuesta incluye lastName Checkpoint: GET /characters funciona + /scalar/v1 muestra los endpoints. --- ## Phase 4: User Story 2 — Crear personaje - [ ] T016 [US2] Añadir LastName y Description a CreateCharacterCommand - [ ] T018 [US2] Actualizar validator: LastName obligatorio, PowerLevel \u0026gt;= 0, Race opcional - [ ] T019 [US2] Actualizar CreateCharacterEndpoint con los nuevos campos y metadata OpenAPI - [ ] T020 [US2] Tests: LastName vacío → 422; PowerLevel = 0 → 201 Checkpoint: POST con PowerLevel: 0 → 201. Con lastName: \u0026#34;\u0026#34; → 422. --- ## Phase 5–6: User Stories 3 y 4 — Actualizar y eliminar - [ ] T023–T029 [US3] Slice completo UpdateCharacter: command, validator, handler, endpoint, tests - [ ] T030–T036 [US4] Slice completo DeleteCharacter: command, handler, endpoint, tests --- ## Phase 7: Frontend — Sincronización de tipos y componentes - [ ] T037 [P] Actualizar character.ts: añadir lastName y description? - [ ] T039 [P] CharacterCard.tsx: mostrar nombre + apellido - [ ] T042 CharacterPage.tsx: mostrar lastName y description usando useCharacter(id) - [ ] T043 npm test en verde (sin errores TypeScript por campos faltantes) --- ## Phase 8: Polish — Verificación final - [ ] T044 dotnet build sin errores ni warnings - [ ] T045 dotnet test — todos los tests en verde (≥ 57 esperado) - [ ] T048 Verificación manual en Scalar de los 5 endpoints - [ ] T050 Marcar spec.md como Implemented Los [P] le indican al agente qué tareas puede ejecutar en paralelo porque tocan ficheros distintos. Las dependencias entre fases son explícitas: la Phase 2 bloquea todo lo demás, y la Phase 7 (frontend) puede ejecutarse en paralelo con las fases 3–6 una vez que el dominio está listo.\n6. Implementación\n/speckit.implement El agente ejecuta las tareas en orden, respetando dependencias y paralelismo. Cada checkpoint actúa como una mini-entrega: cuando termina la Phase 3 tienes el listado y el detalle funcionando en Scalar y desplegable, aunque el CRUD completo todavía no exista.\nLa estrategia MVP que genera el propio agente # Una de las partes más útiles del tasks.md real es la sección de estrategia al final, que el propio agente genera a partir de la spec:\n## MVP (solo US1 — lectura del catálogo) 1. Completar Phase 1 (Setup) 2. Completar Phase 2 (Domain) 3. Completar Phase 3 (US1 — T009 a T015) 4. STOP y VALIDAR: GET /characters con lastName + Scalar funcionando 5. Si MVP aprobado → continuar con Phase 4 ## Entrega incremental Phase 1 + 2 → Base lista Phase 3 → Lectura funcional (demo posible) Phase 4 → Creación funcional (CRUD parcial) Phase 5 → Actualización funcional Phase 6 → Eliminación funcional (CRUD completo) Phase 7 → Frontend actualizado Phase 8 → Verificación y cierre El agente infiere esta estrategia de las historias de usuario y sus prioridades. No es algo que tengas que escribir tú — sale del pipeline.\nPor qué esto cambia el flujo # Entrega incremental real. Cada fase es una unidad de valor que puede llegar a producción de forma independiente. No hay que esperar a tener el DELETE para desplegar el GET.\nEl agente trabaja con scope acotado. Sin este desglose, un agente con mucho contexto puede anticiparse e intentar implementar la actualización mientras trabaja en el listado. Con las fases y sus checkpoints, el scope está claro en cada momento.\nLa ambigüedad se resuelve antes de planificar. El paso /speckit.clarify obliga a responder las preguntas que de otra forma el agente adivinaría. En el proyecto de DragonBall, la regla de PowerLevel \u0026gt;= 0 quedó recogida en la spec y el agente la aplicó correctamente en el validator — sin que nadie tuviera que corregirlo después.\nTrazabilidad por fase. El historial de git refleja las fases: cada commit corresponde a un checkpoint verificable, no a una masa de cambios que nadie puede revisar de un vistazo.\nIntegrando SDD en el flujo completo # Con las tres piezas en su sitio — contexto, skills y specs — el flujo de desarrollo con IA queda así:\n1. Se define la spec de la feature (docs/specs/) 2. El agente lee el CLAUDE.md + la spec y genera la implementación 3. La skill /reviewpr valida el código contra la spec y los estándares del equipo 4. Si hay issues, otro agente los corrige leyendo la misma spec 5. Solo cuando todo pasa, la PR llega al revisor humano El ingeniero interviene en dos momentos: al escribir la spec (donde está el criterio de negocio) y en la revisión final (donde está el criterio de calidad). El resto del ciclo lo gestiona el flujo automático.\nLa spec no solo mejora lo que genera el agente — también hace más fácil la revisión humana. Un revisor que tiene la spec delante puede comprobar en dos minutos si la implementación la cumple. Sin la spec, tiene que inferir la intención a partir del código.\nConclusión # La IA en el ciclo de desarrollo no falla por falta de capacidad técnica. Falla cuando no tiene suficiente información para tomar las decisiones correctas. El contexto del CLAUDE.md le dice cómo trabaja el equipo. Las skills le dicen cómo verificar que lo ha hecho bien. Pero la spec es lo que le dice qué tiene que hacer exactamente.\nSin una spec clara, el agente adivina. Y cuando adivina, genera código que compila, pasa los tests básicos, funciona visualmente\u0026hellip; y aun así tiene inconsistencias en el dominio que solo aparecen en producción.\nEl SDD no es un proceso burocrático: es la forma de que el agente y el equipo hablen el mismo idioma desde el principio. Una spec de treinta líneas puede ahorrar una hora de revisión y evitar un bug en producción. Y con spec-kit, esa spec deja de ser un documento estático: se convierte en el pipeline que lleva del requisito de negocio a las tareas ejecutables por el agente, historia de usuario a historia de usuario, entregando valor en cada paso en lugar de acumular trabajo hasta que todo esté listo.\nCon esto tenemos el ciclo completo: contexto para que el agente entienda el proyecto, specs en fases para que sepa exactamente qué implementar y en qué orden, y skills para que verifique que lo ha hecho bien.\n","date":"14 junio 2026","externalUrl":null,"permalink":"/posts/adoptar-ia-desarrollo-sdd/","section":"Posts","summary":"En el artículo anterior vimos cómo las Skills nos permiten que un agente revise el trabajo de otro, reduciendo el cuello de botella del ingeniero. Pero el análisis acabó con una conclusión incómoda: la skill detectó 9 issues, dos de ellos críticos — datos de dominio perdidos en runtime — y no fueron errores del agente. El agente implementó exactamente lo que se le pidió. El problema estaba en los requisitos: se describió el qué y el cómo, pero nadie pensó en el impacto sobre el resto del sistema.\n","title":"Adoptar IA en el Desarrollo: Spec Driven Development","type":"posts"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/","section":"Avenger Developer","summary":"","title":"Avenger Developer","type":"page"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/tags/claude/","section":"Tags","summary":"","title":"Claude","type":"tags"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/tags/desarrollo/","section":"Tags","summary":"","title":"Desarrollo","type":"tags"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/categories/development/","section":"Categories","summary":"","title":"Development","type":"categories"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/categories/ia/","section":"Categories","summary":"","title":"Ia","type":"categories"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/tags/ia/","section":"Tags","summary":"","title":"Ia","type":"tags"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/tags/productividad/","section":"Tags","summary":"","title":"Productividad","type":"tags"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/tags/spec-driven-development/","section":"Tags","summary":"","title":"Spec-Driven-Development","type":"tags"},{"content":"","date":"14 junio 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"A raíz del primer artículo, continuamos con la serie sobre cómo integrar la IA dentro de nuestro flujo de trabajo. En el primer artículo vimos la importancia de darle contexto para que la IA se aproxime al resultado deseado, pero es algo que siempre está supervisado por una persona que se encarga de revisar cada uno de los pasos que da. Esto provoca que, al final, siempre haya un cuello de botella en el ingeniero que supervisa la IA.\nVamos a mejorar ese flujo, y ahí es donde entran las Skills que tienen los agentes y cómo podemos ir añadiendo verificaciones automáticas al código que está desarrollando la IA.\n¿Cómo lo podemos hacer? # En primer lugar, es fundamental tener unos requisitos muy buenos y que estos sean claros — esta parte la veremos en el siguiente artículo. La otra pieza clave es asegurarnos de que el agente ha implementado lo que nosotros le indicamos. Para ello entran en juego las Skills en Claude Code o los Copilot Agents en GitHub Copilot.\nUn ejemplo claro es crear una skill de Revisor de Pull Request. A este revisor le tenemos que indicar que haga exactamente lo mismo que haría cualquier ingeniero cuando revisa una PR:\nQue el código siga los estándares establecidos en el proyecto Que utilice las librerías acordadas, sin introducir dependencias nuevas Que incluya tests o que al menos cumpla la cobertura mínima establecida De esta forma, cuando un agente termine de realizar su tarea, se le puede pasar esta skill para que indique si hay algún problema respecto a main. Si detecta issues, podemos mandar automáticamente a otro agente a solucionarlos.\n¿Por qué hacerlo con otro agente? # Si miramos nuestro ciclo de desarrollo habitual, una buena práctica (o al menos la que yo sigo) es que los revisores de una pull request no sean las mismas personas que han participado en la creación de esa rama, igual que ocurre con el testing.\nAplicar este mismo principio al flujo con IA tiene mucho sentido: el agente que implementa no es el que revisa. Esto reduce el sesgo y nos da una segunda perspectiva automática antes de que un humano tenga que intervenir.\nEl resultado: menos cuello de botella # Con este enfoque el flujo cambia significativamente:\nUn agente desarrolla la funcionalidad con el contexto y requisitos dados Una skill de revisión analiza el resultado contra nuestros estándares Si hay issues, otro agente los corrige Solo cuando el ciclo automático pasa, llega a la revisión humana El ingeniero sigue siendo el responsable final, pero en lugar de revisar cada línea generada, interviene en un punto mucho más alto del proceso, asegurando que el resultado es lo que el producto requiere. Menos trabajo mecánico, más criterio.\nTalk is cheap, show me the code # Partamos de la base del ejemplo que hicimos en el artículo anterior (GitHub).\n¿Cuál es el requerimiento que queremos hacer? Queremos tener una página similar a esta. En el ejemplo anterior solo teníamos información relativa al backend, por lo que tendremos que darle a Claude el contexto del frontend: qué versión de React usaremos, qué librería de estilos, y que utilice Vite para la creación del proyecto — exactamente igual que hicimos en el artículo pasado con el backend.\nUna vez que el agente nos ha generado la solución, el resultado visual es muy similar al que indicamos en el prompt. Pero ¿está realmente bien implementado? Si ejecutamos nuestra solución vemos el resultado que se queda de esta manera, lo que a primera vista parece correcto ¿cómo analizamos si lo que nos ha dejado es correcto? Aquí es donde entran las skills. Las skills son instrucciones reutilizables que le defines al agente: en lugar de escribir el mismo prompt cada vez, defines una vez cómo debe comportarse y la invocas con un comando como /reviewpr. Por defecto en Claude Code, dado el prompt que tú le indicas, él busca la skill que considera más adecuada. Para flujos más personalizados, yo prefiero adaptar la skill a las necesidades concretas del proyecto. En la web skills.sh puedes encontrar un gran catálogo de skills de la comunidad que puedes usar como base para los tuyos.\nEn nuestro caso, la skill de revisión de PR queda así:\nname: reviewpr description: You are a Senior Engineer conducting a Pull Request review. Your goal is to ensure the code adheres strictly to the project\u0026#39;s Vertical Slice Architecture (VSA), C# Best Practices, React Best Practices. Role: PR Reviewer 🛡️ You are a Senior Engineer conducting a Pull Request review. Your goal is to ensure the code adheres strictly to the project\u0026#39;s Vertical Slice Architecture (VSA), C# Best Practices and React Best Practices. 📋 Review Checklist 1. Vertical Slice Architecture (VSA) - Cohesion: Check if the endpoint, request, response, and logic are in the same folder/slice. - Independence: Ensure this slice doesn\u0026#39;t reuse Request/Response models from other slices. - MinApiLib: Verify the use of PostEndpoint\u0026lt;T\u0026gt;, GetEndpoint\u0026lt;T\u0026gt;, etc., and that Configure() sets the correct OpenAPI metadata. 2. C# \u0026amp; .NET 9 Standards - Async/Await: Verify that CancellationToken is passed to all asynchronous calls (DB, API, etc.). - Logging: Ensure LoggerMessageAttribute is used for structured logging instead of simple _logger.LogInformation(). - Validation: Check if WithValidation() is called in the slice configuration and a FluentValidation class exists. - DI: Ensure [RegisterInIServiceCollection] is present on the handler record. 3. React \u0026amp; Frontend Standards - Architecture \u0026amp; Cohesion: Ensure frontend files (Components, Hooks, Types, API calls) follow a feature-based structure that mirrors the backend\u0026#39;s Vertical Slices. - TypeScript Strictness: Check that strict typing is enforced. The any type is strictly forbidden. - Hooks \u0026amp; State: Verify that custom hooks are used to extract complex business logic from UI components. - Performance: Look for unnecessary re-renders. Ensure useMemo and useCallback are used appropriately. 4. Testing - Integration: Verify there is a test exercising the slice via DelegateAsync. - Mocks: Check that Moq is used only for external infrastructure, not for domain logic. - Frontend Testing: Ensure React components are tested using React Testing Library. 💬 Output Format For each issue found, provide: - Location: File and line number. - Problem: Why it violates our standards. - Suggested Fix: A code snippet showing the correction. Para registrar esta skill en un proyecto, crea el fichero .claude/commands/reviewpr.md en la raíz del repositorio con el contenido anterior. Si quieres que esté disponible en todos tus proyectos, colócala en ~/.claude/commands/reviewpr.md y la tendrás centralizada globalmente.\nLo importante de esta skill es que refleja exactamente los estándares del equipo: VSA en el backend, tipado estricto en el frontend y cobertura mínima de tests. No es una skill genérica — es nuestra definición de \u0026ldquo;código correcto\u0026rdquo; traducida a instrucciones para el agente.\nAl tratarse de un ejemplo didáctico, he decidido unificar la revisión de backend y frontend en una única skill para que se entienda el flujo completo. A nivel profesional, sin embargo, lo más recomendable es tener skills separadas por especialidad: una para backend y otra para frontend. Esto mejora la precisión de cada revisión y facilita el mantenimiento. Dicho esto, también entran en juego otros factores como el consumo de tokens — una skill unificada es más eficiente en ese sentido. Al final dependería de cada caso y del equilibrio que cada equipo quiera establecer.\nEl resultado real # Este es el output que nos devuelve la skill al ejecutar /reviewpr sobre el código generado por el agente:\n🛡️ Code Review — Dragon Ball API\n1. Vertical Slice Architecture (VSA) # ✅ GetCharacters slice — cohesión correcta: GetCharactersQuery, GetCharactersHandler y GetCharactersEndpoint están cada uno en su carpeta de slice. No reutiliza modelos de otros slices.\n⚠️ CreateCharacter slice — CreateCharacterEndpoint.cs\nIssue 1 — CreateCharacterRequest definido en el fichero del endpoint\nLocation: src/Api/Characters/CreateCharacterEndpoint.cs:36-40 Problem: CreateCharacterRequest está definido en la capa Api, no en Application/Characters/CreateCharacter/. Rompe la cohesión: el contrato de entrada del slice vive fuera del slice. Suggested Fix: Mover a src/Application/Characters/CreateCharacter/CreateCharacterRequest.cs Issue 2 — Validación manual con try/catch ValidationException\nLocation: src/Api/Characters/CreateCharacterEndpoint.cs:15-32 Problem: MinApiLib ofrece WithValidation() en Configure() para manejar FluentValidation automáticamente. El try/catch manual es boilerplate innecesario. 2. C# \u0026amp; .NET Standards # Issue 3 — ImageUrl ausente en CreateCharacterCommand y CreateCharacterRequest 🔴\nLocation: src/Application/Characters/CreateCharacter/CreateCharacterCommand.cs:3-7 Problem: Character tiene ImageUrl como campo obligatorio (default!), pero ni el command ni el request lo incluyen. Los personajes creados vía API siempre tendrán ImageUrl nulo en runtime. Issue 4 — InMemoryCharacterRepository.Add no copia ImageUrl 🔴\nLocation: src/Application/Characters/InMemoryCharacterRepository.cs:27-33 Problem: El método Add construye un nuevo Character sin incluir ImageUrl. Incluso si se arregla el command, el dato se perdería aquí. Issue 5 — PowerLevel como int con valores que rozan el límite 🟡\nProblem: Broly tiene PowerLevel = 1_400_000_000. El máximo de int es 2_147_483_647. Cualquier personaje nuevo puede causar overflow silencioso. Solución: cambiar a long. Issue 6 — Sin OpenAPI metadata en los endpoints 🟢\nProblem: Ningún endpoint implementa Configure() para describir summary, tags o códigos de respuesta. La documentación Scalar/OpenAPI está vacía. 3. Testing # Issue 7 — Tests de integración sin CancellationToken 🟡\nLocation: GetCharactersEndpointTests.cs:17,25,35 y CreateCharacterEndpointTests.cs:19,29,43,53,61,71 Problem: Las llamadas _client.GetAsync y _client.PostAsJsonAsync no pasan TestContext.Current.CancellationToken (convención obligatoria del proyecto). Issue 8 — CreateCharacterHandlerTests usa CancellationToken.None 🟡\nLocation: CreateCharacterHandlerTests.cs:26,37,46 Problem: Mismo problema que el issue 7. Issue 9 — Sin endpoint ni tests para GET /characters/{id} 🟡\nProblem: CharacterPage.tsx del frontend lo necesita y actualmente hace un workaround cargando todos los personajes. # Severidad Descripción 3 🔴 Alta ImageUrl ausente en command y request — personajes sin imagen al crear 4 🔴 Alta InMemoryCharacterRepository.Add no copia ImageUrl 1,2,5,7,8,9 🟡 Media Problemas de estándares (VSA, CancellationToken, overflow) 6 🟢 Baja Sin metadatos OpenAPI en Configure() La skill ha detectado 9 issues sin que ningún humano haya tenido que leer el código. Dos de ellos son de severidad alta (datos perdidos en runtime) y el resto son problemas de estándares que en una revisión manual podrían pasar desapercibidos. Exactamente para esto la definimos.\n¿Pero cuál es el motivo por el que aparecen estos problemas? Se trata de un problema muy común en la definición de requerimientos: se ha descrito el cómo y el qué, pero no el impacto en el resto del producto. En este escenario, en el primer análisis no se tuvo en cuenta qué ocurre en el momento en el que se crea un personaje. Se modificó el modelo para poder mostrar una imagen tal y como se pedía en el resumen, pero cumplir el requisito visual no justifica introducir inconsistencias en el dominio.\n¿Manual o automatizado en la PR? # ¿Y dónde podemos ejecutar esta skill? Se puede ejecutar desde el mismo IDE en el que estás desarrollando. Si la skill se ha colocado en la ubicación correcta de Claude Code, se carga de forma automática y con escribir /reviewpr la ejecuta siguiendo las instrucciones que tú indicas.\nEsto es algo propio de Claude Code: si quieres que una skill esté disponible únicamente en un proyecto, la defines dentro de ese repositorio. Si por el contrario quieres que esté disponible en todos tus proyectos, la puedes colocar en la raíz de configuración global y así las tienes centralizadas en un único sitio.\nTeniendo esto claro, merece la pena comparar los dos enfoques de ejecución, porque cada uno tiene su lugar:\nEjecución manual (IDE) Ejecución automática (PR) ¿Quién la lanza? El propio desarrollador GitHub Actions, sin intervención humana ¿Cuándo? Cuando el desarrollador decide En cada apertura o actualización de PR Resultado Visible en el terminal del IDE Comentario automático en la PR Coste de tokens Solo cuando se invoca En cada push a la PR Ideal para Iteración rápida durante el desarrollo Validación formal antes del review humano La ejecución manual es útil mientras estás desarrollando: lanzas /reviewpr cada vez que quieres un feedback rápido sin salir del IDE. Es inmediata y no consume tokens si no la usas.\nLa ejecución automática en la PR es la que aporta más valor al equipo: garantiza que ninguna PR llega al revisor humano sin haber pasado por la skill, independientemente de si el desarrollador se acordó de ejecutarla o no. Es la diferencia entre una convención y un control.\nLo habitual es usar ambas: el desarrollador ejecuta la skill manualmente durante el desarrollo para corregir sobre la marcha, y la pipeline actúa como red de seguridad en el momento de abrir la PR.\nLa idea es sencilla: cada vez que se abra o actualice una PR, GitHub Actions lanza Claude Code en modo no interactivo, ejecuta la skill /reviewpr sobre los cambios y publica el resultado como comentario en la propia PR. Para ello necesitamos la ANTHROPIC_API_KEY como secreto en el repositorio y el siguiente workflow como punto de partida:\nNota: este workflow es orientativo. La sintaxis exacta puede variar según la versión de Claude Code instalada — consulta la documentación oficial para los flags más actualizados.\nname: PR Review on: pull_request: branches: [main] jobs: review: runs-on: ubuntu-latest permissions: pull-requests: write contents: read steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Claude Code run: npm install -g @anthropic-ai/claude-code - name: Run reviewpr skill id: review env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | claude --print \u0026#34;/reviewpr\u0026#34; \u0026gt; review_output.txt echo \u0026#34;result\u0026lt;\u0026lt;EOF\u0026#34; \u0026gt;\u0026gt; $GITHUB_OUTPUT cat review_output.txt \u0026gt;\u0026gt; $GITHUB_OUTPUT echo \u0026#34;EOF\u0026#34; \u0026gt;\u0026gt; $GITHUB_OUTPUT - name: Post review as PR comment uses: actions/github-script@v7 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `## 🛡️ Revisión automática de PR\\n\\n${{ steps.review.outputs.result }}` }) Con esto, cada PR recibe automáticamente un comentario con el resultado de la revisión antes de que ningún humano la haya mirado. El revisor humano llega con el análisis hecho y puede centrarse en el criterio, no en la detección mecánica de problemas.\nConclusión # En este artículo hemos dado un paso más en la integración de IA dentro del flujo de desarrollo. Partimos del problema real: el ingeniero seguía siendo el cuello de botella porque tenía que supervisar cada paso del agente. La solución no es eliminar esa supervisión, sino elevarla: en lugar de revisar línea a línea, definimos los estándares del equipo como una skill y dejamos que sea otro agente quien los verifique.\nEl resultado es un flujo donde la IA no solo genera código, sino que también lo revisa siguiendo nuestras propias reglas. El ingeniero sigue tomando las decisiones importantes, pero ya no pierde tiempo en la detección mecánica de problemas.\nSin embargo, como hemos visto con los 9 issues detectados, hay un límite claro: la skill puede encontrar lo que falta o lo que está mal implementado, pero no puede compensar unos requisitos mal definidos desde el principio. El agente implementó exactamente lo que se le pidió, y aun así el resultado tenía inconsistencias en el dominio.\nEsto nos lleva directamente al siguiente reto: ¿cómo escribimos requisitos que sean lo suficientemente precisos para que el agente no tenga que adivinar nada? En el próximo artículo exploraremos el Spec Driven Development, una forma de definir las especificaciones antes de escribir una sola línea de código, de manera que tanto los agentes como los ingenieros partan del mismo contexto y hablen el mismo lenguaje.\nNota: puedes ver el resultado en el repositorio del ejemplo.\n","date":"1 junio 2026","externalUrl":null,"permalink":"/posts/adoptar-ia-desarrollo-skill/","section":"Posts","summary":"A raíz del primer artículo, continuamos con la serie sobre cómo integrar la IA dentro de nuestro flujo de trabajo. En el primer artículo vimos la importancia de darle contexto para que la IA se aproxime al resultado deseado, pero es algo que siempre está supervisado por una persona que se encarga de revisar cada uno de los pasos que da. Esto provoca que, al final, siempre haya un cuello de botella en el ingeniero que supervisa la IA.\n","title":"Adoptar IA en el Desarrollo: Skills","type":"posts"},{"content":"La IA ha dejado de ser un hype. Hemos pasado de que la usaban cuatro valientes a que hoy en día prácticamente nadie en el sector se queda al margen. Se habla mucho de que seremos la última generación de desarrolladores que hemos escrito código de verdad, de que la IA lo puede hacer todo\u0026hellip; pero ¿cuál es la realidad hoy? ¿Cómo la podemos ir integrando de forma sensata? En esta serie de posts voy a ir contando cómo lo uso yo en mi día a día con proyectos .NET.\nEl primer contacto # La primera vez que te enfrentas a una herramienta de IA como Claude, reconozcámoslo, el caso de uso más habitual es tirarle un error de compilación o una duda puntual. Le preguntas, te propone algo, encaja, y piensas: \u0026ldquo;esto mola\u0026rdquo;. Otra funcionalidad que engancha rápido es el autocompletado predictivo: escribes function Fibonacci y ya te tiene el cuerpo entero listo.\nUna vez que ves que te complementa, vas cogiendo confianza y empiezas a pedirle cosas más grandes: \u0026ldquo;añádeme un nuevo endpoint en nuestra API de pedidos\u0026rdquo;. Aquí es donde te puedes encontrar con dos escenarios muy diferentes:\nEl agente analiza tu proyecto, entiende qué tienes y genera un endpoint siguiendo tus estándares. El agente entra en \u0026ldquo;modo imaginación\u0026rdquo; y te entrega algo que tiene buena pinta pero que claramente no lo ha escrito nadie del equipo: usa Controllers en lugar de Minimal APIs, cambia la forma de definir las clases, introduce record donde nunca los usáis\u0026hellip; ¿Por qué ocurre esto? Porque al agente le pasa exactamente lo mismo que a cualquier ingeniero que entra nuevo en un proyecto: no sabe qué guidelines usa el equipo, qué arquitectura se sigue, qué librerías están aprobadas. Si no se lo cuentas, improvisa, igual que haría una persona.\n¿Cómo adoptamos la IA en el equipo? # Para mí hay una diferencia enorme entre integrar la IA en un proyecto existente y arrancar uno nuevo con ella desde el principio. Si tienes un proyecto en curso, tienes que pensar bien el proceso. Si arrancas desde cero, tienes la ventaja de establecer las bases desde el primer commit.\nSi el proyecto ya existe, el primer movimiento es ejecutar /init desde Claude Code en la raíz del repositorio. El comando escanea el repo y genera un CLAUDE.md automático con lo que infiere: estructura de carpetas, tecnologías detectadas, comandos del proyecto. Es un punto de partida útil, pero casi siempre necesita corrección: puede malinterpretar la arquitectura, incluir carpetas irrelevantes como bin/ u obj/, o asumir patrones que no usáis. Tómatelo como un borrador que hay que revisar con el equipo antes de hacer commit.\nOtro error habitual al arrancar en un repo grande es querer dar todo el contexto de golpe. Lo que funciona mejor es ir por capas: empieza con el CLAUDE.md básico (stack y estructura), comprueba que el agente genera algo coherente con una tarea pequeña, y ve añadiendo guidelines a medida que detectas que se desvía. Si el agente comete el mismo error dos veces, eso es una señal clara de que falta esa regla en el fichero de contexto.\nPaso 1: Qué incluir en el fichero de contexto # Un buen CLAUDE.md no es un resumen del README: es una guía de trabajo. La pregunta que tienes que responder es ¿qué necesitaría saber un ingeniero nuevo el primer día para no romper nada y encajar en el equipo? Eso mismo es lo que necesita el agente.\nPara un proyecto .NET, como mínimo debería cubrir:\n## Stack - .NET 10, ASP.NET Core Minimal APIs con MinApiLib - CQRS sin MediatR: handlers inyectados directamente vía [FromServices] - FluentValidation para validaciones (devuelve 422, no 400) - xUnit v3 + WebApplicationFactory para tests de integración ## Estructura del proyecto - src/Api → endpoints y configuración de la app (record : Post/Get con MinApiLib) - src/Application → handlers, commands, queries (vertical slices) - src/Domain → entidades e interfaces de repositorio - tests/ → tests unitarios e integración ## Comandos habituales dotnet build dotnet test dotnet run --project src/Api ## Convenciones importantes - Los endpoints son records que heredan de Post/Get/etc. de MinApiLib - El binding del body se hace con [FromBody] en HandleAsync, nunca en el constructor - ValidationProblem debe pasar statusCode: StatusCodes.Status422UnprocessableEntity - No añadir librerías nuevas sin consultarlo primero Con esto el agente ya sabe con qué trabaja, dónde vive cada cosa y cómo ejecutar el proyecto. Es suficiente para empezar. El resto de las reglas las irás añadiendo a medida que detectes que se desvía.\nPaso 2: Documentar las guidelines del equipo # Con el contexto general no basta. El siguiente paso es crear una carpeta docs/ en el repositorio con al menos estos ficheros:\narchitecture.md: qué arquitectura usamos (por ejemplo, Vertical Slice Architecture), qué patrones están prohibidos, cómo se organizan las features. guidelines.md: nomenclatura de clases, cómo se nombran los endpoints, estilo de código, reglas de EF Core (¿usamos migraciones automáticas o manuales?), etc. decisions/: una carpeta con ADRs (Architecture Decision Records). Cada fichero explica una decisión técnica y el motivo: por qué usamos Minimal APIs en lugar de Controllers, por qué elegimos MediatR, etc. Esto es especialmente valioso para la IA, porque le da el por qué, no solo el qué. Cuanto más contexto le des, más coherente será el código que genere.\nPaso 3: Definir qué acciones queremos que haga la IA # Una vez que el agente tiene contexto del proyecto y de las guidelines, el siguiente paso es establecer para qué tareas lo vamos a usar de forma recurrente. Las que a mí más valor me aportan en proyectos .NET son:\nRevisión de PRs: que compruebe si el código que se quiere mergear cumple con las guidelines, si hay problemas de seguridad evidentes o si falta cobertura de tests. Generación de tests: dado un handler o un endpoint, que genere los tests unitarios y de integración correspondientes siguiendo la estructura de tests/. Revisión de seguridad: detección de problemas como inyecciones SQL, exposición de datos sensibles en logs, endpoints sin autorización, etc. Refactor guiado: cuando detectas un problema de deuda técnica, pedirle que proponga el refactor respetando la arquitectura del proyecto. Paso 4: Establecer reglas de comportamiento en Claude Code # Claude Code permite definir reglas persistentes que el agente aplica en todas las conversaciones del proyecto, dentro del propio CLAUDE.md. Por ejemplo:\n## Reglas para el agente - Usa siempre Minimal APIs, nunca Controllers. - Los tests de integración deben usar Testcontainers, nunca mocks de base de datos. - Aplica el patrón Vertical Slice: cada feature va en su propia carpeta dentro de Application/. - No añadas librerías nuevas sin consultarlo primero. - Si detectas código sin tests, indícalo pero no los generes salvo que se te pida explícitamente. Estas reglas hacen que el agente trabaje dentro de los límites que el equipo ha establecido, reduciendo drásticamente el ruido en las revisiones.\nPaso 5: Mantener el contexto vivo # El CLAUDE.md tiene el mismo problema que cualquier documentación: si nadie lo cuida, queda obsoleto. Y un contexto obsoleto es casi peor que no tener ninguno, porque el agente genera código confiado en información incorrecta.\nAlgunas prácticas que funcionan bien:\nTrátalo como código: el CLAUDE.md vive en el repositorio y pasa por revisión en la PR igual que cualquier otro fichero. Si se cambia la arquitectura, la PR que introduce ese cambio también actualiza el contexto. Úsalo como señal: cada vez que el agente comete un error por falta de contexto, es una tarea pendiente en el fichero. No lo corrijas solo en el chat; escríbelo. No lo dejes crecer sin control: un CLAUDE.md de 500 líneas es tan inútil como uno vacío. Si crece demasiado, mueve el detalle a docs/ y deja en el fichero principal solo lo esencial. El agente necesita contexto accionable, no una novela. Ejemplo real: añadir un endpoint en la API de DragonBall # Vamos a ver la diferencia en la práctica. El código completo está disponible en GitHub. Tenemos una API de personajes de DragonBall con esta estructura:\nsrc/ ├── Api/ │ ├── Program.cs │ └── Characters/ │ └── GetCharactersEndpoint.cs ├── Application/ │ └── Characters/ │ └── GetCharacters/ │ ├── GetCharactersQuery.cs │ └── GetCharactersHandler.cs ├── Domain/ │ └── Characters/ │ └── Character.cs tests/ └── DragonBall.Tests/ └── Integration/ └── GetCharactersEndpointTests.cs Le pedimos a Claude: \u0026ldquo;Añade el endpoint POST /characters para crear un personaje\u0026rdquo;.\nSin contexto: lo que hace Claude por defecto # Sin CLAUDE.md ni documentación de guidelines, Claude genera algo así:\n// CharactersController.cs — ¡Controllers! Nadie del equipo usa esto [ApiController] [Route(\u0026#34;api/[controller]\u0026#34;)] public class CharactersController : ControllerBase { private readonly AppDbContext _db; public CharactersController(AppDbContext db) { _db = db; } [HttpPost] public async Task\u0026lt;IActionResult\u0026gt; CreateCharacter([FromBody] CreateCharacterDto dto) { var character = new Character { Name = dto.Name, Race = dto.Race }; _db.Characters.Add(character); await _db.SaveChangesAsync(); return Ok(character); } } Problemas evidentes: Controller en lugar de Minimal API, sin MinApiLib, sin FluentValidation, sin handler independiente, sin tests. Un revisor lo rechazaría en la PR.\nCon contexto: lo que hace Claude conociendo el proyecto # Con el CLAUDE.md en su lugar, Claude genera la feature completa siguiendo la estructura Vertical Slice y las convenciones de MinApiLib que ya existen en el proyecto:\nsrc/Api/Characters/CreateCharacterEndpoint.cs\nusing Application.Characters.CreateCharacter; using FluentValidation; using Microsoft.AspNetCore.Mvc; using MinApiLib.Endpoints; namespace Api.Characters; public record CreateCharacter() : Post(\u0026#34;/characters\u0026#34;) { public async Task\u0026lt;IResult\u0026gt; HandleAsync( [FromBody] CreateCharacterRequest body, [FromServices] CreateCharacterHandler handler, CancellationToken cancellationToken) { try { var character = await handler.Handle( new CreateCharacterCommand(body.Name, body.Race, body.PowerLevel, body.Affiliation), cancellationToken); return Results.Created($\u0026#34;/characters/{character.Id}\u0026#34;, character); } catch (ValidationException ex) { var errors = ex.Errors .GroupBy(e =\u0026gt; e.PropertyName) .ToDictionary( g =\u0026gt; g.Key, g =\u0026gt; g.Select(e =\u0026gt; e.ErrorMessage).ToArray()); return Results.ValidationProblem(errors, statusCode: StatusCodes.Status422UnprocessableEntity); } } } public sealed record CreateCharacterRequest( string Name, string Race, int PowerLevel, string Affiliation); src/Application/Characters/CreateCharacter/CreateCharacterCommand.cs\nnamespace Application.Characters.CreateCharacter; public sealed record CreateCharacterCommand( string Name, string Race, int PowerLevel, string Affiliation); src/Application/Characters/CreateCharacter/CreateCharacterHandler.cs\nusing Domain.Characters; using FluentValidation; namespace Application.Characters.CreateCharacter; public sealed class CreateCharacterHandler( ICharacterRepository repository, IValidator\u0026lt;CreateCharacterCommand\u0026gt; validator) { public async Task\u0026lt;Character\u0026gt; Handle(CreateCharacterCommand command, CancellationToken cancellationToken) { await validator.ValidateAndThrowAsync(command, cancellationToken); var character = new Character { Name = command.Name, Race = command.Race, PowerLevel = command.PowerLevel, Affiliation = command.Affiliation }; return repository.Add(character); } } src/Application/Characters/CreateCharacter/CreateCharacterValidator.cs\nusing FluentValidation; namespace Application.Characters.CreateCharacter; public sealed class CreateCharacterValidator : AbstractValidator\u0026lt;CreateCharacterCommand\u0026gt; { public CreateCharacterValidator() { RuleFor(x =\u0026gt; x.Name) .NotEmpty().WithMessage(\u0026#34;El nombre es obligatorio.\u0026#34;) .MaximumLength(100).WithMessage(\u0026#34;El nombre no puede superar los 100 caracteres.\u0026#34;); RuleFor(x =\u0026gt; x.Race) .NotEmpty().WithMessage(\u0026#34;La raza es obligatoria.\u0026#34;) .MaximumLength(50).WithMessage(\u0026#34;La raza no puede superar los 50 caracteres.\u0026#34;); RuleFor(x =\u0026gt; x.PowerLevel) .GreaterThan(0).WithMessage(\u0026#34;El nivel de poder debe ser mayor que 0.\u0026#34;); RuleFor(x =\u0026gt; x.Affiliation) .NotEmpty().WithMessage(\u0026#34;La afiliación es obligatoria.\u0026#34;) .MaximumLength(100).WithMessage(\u0026#34;La afiliación no puede superar los 100 caracteres.\u0026#34;); } } tests/DragonBall.Tests/Integration/CreateCharacterEndpointTests.cs\nusing System.Net; using System.Net.Http.Json; using Domain.Characters; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; namespace DragonBall.Tests.Integration; public sealed class CreateCharacterEndpointTests(WebApplicationFactory\u0026lt;Program\u0026gt; factory) : IClassFixture\u0026lt;WebApplicationFactory\u0026lt;Program\u0026gt;\u0026gt; { private readonly HttpClient _client = factory.CreateClient(); [Fact] public async Task POST_characters_with_valid_body_returns_201() { var body = new { Name = \u0026#34;Bardock\u0026#34;, Race = \u0026#34;Saiyan\u0026#34;, PowerLevel = 10000, Affiliation = \u0026#34;None\u0026#34; }; var response = await _client.PostAsJsonAsync(\u0026#34;/characters\u0026#34;, body); response.StatusCode.Should().Be(HttpStatusCode.Created); } [Fact] public async Task POST_characters_returns_created_character_with_id() { var body = new { Name = \u0026#34;Bardock\u0026#34;, Race = \u0026#34;Saiyan\u0026#34;, PowerLevel = 10000, Affiliation = \u0026#34;None\u0026#34; }; var response = await _client.PostAsJsonAsync(\u0026#34;/characters\u0026#34;, body); var character = await response.Content.ReadFromJsonAsync\u0026lt;Character\u0026gt;(); character.Should().NotBeNull(); character!.Id.Should().BeGreaterThan(0); character.Name.Should().Be(\u0026#34;Bardock\u0026#34;); } [Fact] public async Task POST_characters_with_empty_name_returns_422() { var body = new { Name = \u0026#34;\u0026#34;, Race = \u0026#34;Saiyan\u0026#34;, PowerLevel = 9000, Affiliation = \u0026#34;Z Fighters\u0026#34; }; var response = await _client.PostAsJsonAsync(\u0026#34;/characters\u0026#34;, body); response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); } [Fact] public async Task POST_characters_with_zero_power_level_returns_422() { var body = new { Name = \u0026#34;Goku\u0026#34;, Race = \u0026#34;Saiyan\u0026#34;, PowerLevel = 0, Affiliation = \u0026#34;Z Fighters\u0026#34; }; var response = await _client.PostAsJsonAsync(\u0026#34;/characters\u0026#34;, body); response.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); } } La diferencia es brutal. El segundo resultado está listo para pasar la revisión de PR: usa MinApiLib con el patrón de record que ya tiene el proyecto, separa el handler y el validador en su propia carpeta de vertical slice, incluye validación con FluentValidation devolviendo 422 correctamente, y tiene los tests de integración con WebApplicationFactory apuntando a la app real.\nTodo esto con el mismo prompt. La única diferencia es el contexto que le hemos dado al agente.\nDicho esto, el contexto reduce los errores pero no los elimina. El agente puede alucinar aunque tenga un CLAUDE.md perfecto: introducir un using que no existe, saltarse una convención que creías bien documentada o generar un test que compila pero no prueba lo que debería. Por eso los tests no son opcionales: son la red de seguridad que te permite delegar con confianza. Si el agente genera código y los tests pasan, tienes una garantía real. Si no hay tests, solo tienes fe.\nCuando el agente se equivoca de forma recurrente en lo mismo, no lo corrijas solo en el chat: añade esa corrección al CLAUDE.md o a los docs. Así el error ocurre una vez, no cien.\nConclusión # Adoptar la IA en un equipo de desarrollo no es instalar una extensión y olvidarse. Requiere el mismo esfuerzo que incorporar a un ingeniero nuevo: darle contexto, explicarle cómo trabajamos y definir qué se espera de él. La diferencia es que, una vez que tiene esa información, escala infinitamente mejor.\nEn el próximo post hablaré de cómo continuar con otras partes del ciclo de desarrollo donde realmente podamos ver el impacto de la IA.\n","date":"24 mayo 2026","externalUrl":null,"permalink":"/posts/adoptar-ia-desarrollo/","section":"Posts","summary":"La IA ha dejado de ser un hype. Hemos pasado de que la usaban cuatro valientes a que hoy en día prácticamente nadie en el sector se queda al margen. Se habla mucho de que seremos la última generación de desarrolladores que hemos escrito código de verdad, de que la IA lo puede hacer todo… pero ¿cuál es la realidad hoy? ¿Cómo la podemos ir integrando de forma sensata? En esta serie de posts voy a ir contando cómo lo uso yo en mi día a día con proyectos .NET.\n","title":"Cómo adoptar la IA en tu ciclo de desarrollo","type":"posts"},{"content":"","date":"24 mayo 2026","externalUrl":null,"permalink":"/tags/csharp/","section":"Tags","summary":"","title":"Csharp","type":"tags"},{"content":"","date":"24 mayo 2026","externalUrl":null,"permalink":"/tags/dotnet/","section":"Tags","summary":"","title":"Dotnet","type":"tags"},{"content":"","date":"24 mayo 2026","externalUrl":null,"permalink":"/tags/team/","section":"Tags","summary":"","title":"Team","type":"tags"},{"content":"","date":"1 noviembre 2025","externalUrl":null,"permalink":"/tags/growing/","section":"Tags","summary":"","title":"Growing","type":"tags"},{"content":"Hace unas semanas tuve la fortuna de asistir al concierto de Enrique Bunbury junto con su mítica banda, El Huracán Ambulante, en el pabellón Príncipe Felipe. Hacía muchos años que no tocaban juntos, y bastaron unos segundos sobre el escenario para notar las ganas acumuladas, la complicidad intacta.\nPara quien no los conozca, El Huracán Ambulante está formado por Jordi Mena (guitarra solista), Del Morán (bajo), Ramón Gacías (batería), Copi (teclados), Ana Belén Estaje (violín y coros), Luismi Romero (percusión) y Javier García Vega (trombón). Un conjunto de músicos tan distintos entre sí como complementarios.\nDurante el concierto, todo encajó y sono a la perfección. La guitarra, el violín, el trombón, la voz… todo sonaba con una precisión y emoción que solo tienen los equipos que se entienden sin hablar. En ese instante, mi atención se fue inevitablemente hacia la figura del líder: Enrique Bunbury.\nHabía algo fascinante en cómo cada miembro de la banda tenía su espacio, su momento para brillar, y aun así el conjunto sonaba como un reloj suizo. Se notaba que había un trabajo profundo detrás: respeto, confianza y una dirección clara. Y pensé en todo lo que eso dice del liderazgo.\nLo interesante no es solo cómo lidera hoy, sino cómo ha evolucionado. Porque Bunbury no siempre fue así. Quienes seguimos su carrera recordamos los tiempos de Héroes del Silencio, aquella banda que llevó el rock español por media Europa —Alemania, Polonia, Latinoamérica— y que, en el punto más alto de su éxito, se disolvió. En aquel entonces, quizás por la juventud, el ego o el vértigo del éxito, Bunbury lideraba desde la intensidad, la exigencia… y también desde cierta tiranía. Era el centro del huracán, pero también la causa de su ruptura.\nAños después, verlo sobre el escenario con la misma pasión pero con más equilibrio, más escucha y más conexión con su banda, muestra la evolución de un líder que ha aprendido a liderar desde la madurez. Un líder que ya no necesita ser el protagonista absoluto para brillar. Que entiende que el talento crece cuando se comparte y que el éxito es más sólido cuando todos se sienten parte de él.\n🧭 Cómo evoluciona un líder # Nadie nace sabiendo. Y en el caso de un líder, su verdadera evolución comienza cuando comete errores. Todos los cometemos —y más de los que nos gustaría—, pero lo importante no es evitarlos, sino aprender de ellos. Entender en qué te has equivocado, reconocerlo sin miedo y usar esa experiencia para actuar de forma diferente la próxima vez.\nEsa es la base del crecimiento: convertir el error en aprendizaje. Y, como todo proceso, depende mucho del bagaje de cada uno. En el mundo IT, por ejemplo, no es lo mismo venir de un perfil técnico que de un perfil de negocio: los fallos pueden ser distintos, pero el impacto y la oportunidad de aprendizaje suelen ser los mismos.\n🚀 De individual contributor a líder # En muchas organizaciones se da por hecho que una persona brillante en su rol individual está preparada para liderar equipos. Pero ser un gran profesional no garantiza ser un buen líder.\nOcurre algo parecido a lo que vemos en el deporte: un jugador con talento, que entiende el juego y anima a sus compañeros, no necesariamente será un gran entrenador. Son dos papeles distintos, con habilidades distintas.\nLiderar implica pasar de ejecutar a hacer que otros ejecuten bien, de destacar tú a hacer que los demás brillen. Y eso requiere desarrollar competencias nuevas —comunicación, empatía, gestión de conflictos, visión a largo plazo— que no siempre se aprenden de manera natural. Muchas veces, al empezar, uno actúa según su propio sentido común, y ahí es donde cada persona traza su propio camino de evolución.\nErrores mas comunes # Uno de los errores más importantes al convertirse en líder es pensar que puedes seguir resolviendo los problemas de la misma forma que lo hacías como individual contributor.\nHay muchos desarrolladores brillantes que, por distintas circunstancias, terminan en la vertiente del management. En muchos casos, el cambio viene motivado por razones económicas: en ciertas organizaciones, la única forma de crecer salarialmente es asumiendo un rol de liderazgo. Pero esa transición implica empezar, en cierto modo, desde cero. Podías ser un desarrollador excepcional, pero como manager eres una incógnita.\n¿La razón? Porque las habilidades necesarias son completamente diferentes. No tienen nada que ver.\nPara quienes venimos de un perfil técnico, este cambio puede ser especialmente difícil. Resulta complicado no bajar al barro, no intentar entender cada detalle técnico. Pero aprender a soltar no significa volverse alguien desconectado de la realidad técnica. Significa usar ese conocimiento para ayudar al equipo a crecer, aprender y confiar en ti. Esa es una de las grandes ventajas de un manager con pasado técnico.\nDebemos evitar frases como “eso se hace en cinco minutos” o “eso está tirado”. Sin embargo, también debemos ser capaces de detectar cuando el esfuerzo estimado no tiene sentido. En esos momentos, hay dos caminos posibles:\nHacer un challenge al equipo, para entender si hay algo que se está valorando de forma distinta o algún requerimiento mal interpretado.\nConfiar en el criterio del equipo, porque quizás están viendo aspectos que tú ya no controlas tan de cerca.\nNo hay una fórmula mágica. Cada situación exige una decisión distinta. Como líder, tu papel es evaluar qué conviene más en cada momento: ¿tienes margen de tiempo?, ¿hay un deadline crítico?, ¿puedes permitirte explorar o necesitas ejecutar?\nSolo viviendo esas situaciones aprendes a encontrar el equilibrio entre la dirección técnica y el liderazgo.\nOtro de los errores más comunes, es perderte en los aspectos tecnicos y dejar de lado el aspecto humano y de las personas. Foco en la tecnologia por encima de todo, es algo normal, en roles tecnicos siempre nos apasiona la tecnolgía pero ahora tu función principal no es esa. Tienes que ver que acciones puedes hacer para que mejor el funcionamiento de todo el conjunto, antes tu impacto era un x1, o bueno si eras muy bueno en el mejor de los casos podriamos decir que eres un x2\u0026hellip; pero si tu tienes un equipo de N personas cual es tu impacto Nx lo cual quiere decir que tienes que pensar muy mucho en que aspectos son los prioritarios para ti. Por ejemplo el analizar como migrar de versión, cuanto de impacto tiene? En el producto? Es posible para que sea mucho más mantenible y escalable en el tiempo \u0026hellip; pero por ejemplo saber como esta cada miembro del equipo, que aspectos le preocupan, en que le puedes ayudar, que le hace falta para que rinda como sabe, para que este contento y que su impacto sume al equipo porque vamos a lo que comentaba en el anterior post la importancia del equipo por encima de todo.\nLa visión estrategica # Otro punto importante que debemos tener claro es cuál es nuestra visión a largo plazo. No se trata solo de mirar el presente, sino de prepararnos para lo que está por venir. Este es uno de los grandes retos en el liderazgo: muchos líderes se enfocan únicamente en el corto plazo.\nEl corto plazo es, en cierto modo, no levantar la mirada del barro: estar atrapado en el último detalle técnico. Imagina que ves una tendencia clara en tu producto o en tu tecnología: la posibilidad de incorporar un nuevo lenguaje, migrar a otro cloud o adoptar una tecnología que sabes que inevitablemente llegará. Ante ese escenario, tienes dos opciones: esperar a que ocurra y reaccionar cuando ya esté encima, o anticiparte.\nAnticiparse significa preparar al equipo: planificar formación, contratar perfiles con ese conocimiento, apoyarte en expertos de la compañía o explorar pruebas de concepto que permitan aprender con tiempo. Hay muchas alternativas, y la visión que tengas determinará tu capacidad de anticipación, de tomar mejores decisiones y de posicionar al equipo para el futuro.\nAhora bien, ¿cómo se desarrolla esa visión? No hay una única respuesta. En gran medida depende de la evolución del propio líder: de su capacidad para mantenerse cerca del producto, entendiendo qué decisiones impactan en la tecnología, y al mismo tiempo estar conectado con la realidad técnica, identificando cómo las decisiones tecnológicas pueden aportar valor al producto. Es ese equilibrio el que permite anticipar, preparar y guiar al equipo en la dirección correcta.\nResumen # Nadie nace sabiendo. Cuando asumes un rol de liderazgo no te entregan un manual de instrucciones, y es completamente normal cometer muchos errores al principio —nos pasa a todos.\nMi consejo es apoyarte en quienes ya han recorrido ese camino: aprende de tus compañeros en roles similares, pregunta, pide ayuda y también ofrécela. Fórmate, lee, observa y mantén siempre una actitud abierta al aprendizaje.\nDesde esa mentalidad, tu evolución será natural y constante. Y esto no aplica solo al liderazgo, sino a cualquier puesto y, en realidad, a cualquier ámbito de la vida.\n👉 ¿Y tú? ¿En qué momento estás de tu camino como líder: aprendiendo, soltando, o enseñando?\n","date":"1 noviembre 2025","externalUrl":null,"permalink":"/posts/evolucion-lider/","section":"Posts","summary":"Hace unas semanas tuve la fortuna de asistir al concierto de Enrique Bunbury junto con su mítica banda, El Huracán Ambulante, en el pabellón Príncipe Felipe. Hacía muchos años que no tocaban juntos, y bastaron unos segundos sobre el escenario para notar las ganas acumuladas, la complicidad intacta.\n","title":"La evolución del lider y su crecimiento","type":"posts"},{"content":"","date":"1 noviembre 2025","externalUrl":null,"permalink":"/tags/leadership/","section":"Tags","summary":"","title":"Leadership","type":"tags"},{"content":"","date":"1 noviembre 2025","externalUrl":null,"permalink":"/categories/managment/","section":"Categories","summary":"","title":"Managment","type":"categories"},{"content":"","date":"1 noviembre 2025","externalUrl":null,"permalink":"/tags/managment/","section":"Tags","summary":"","title":"Managment","type":"tags"},{"content":"En los últimos meses he estado leyendo más de lo habitual, buscando libros y artículos que me ayuden en mi nuevo rol, mucho más centrado en la gestión de personas. Este cambio me exige desarrollar habilidades diferentes a las que antes dominaba.\nEn gran parte de lo que leo aparece una idea común: la importancia de un buen líder. Un líder no solo destaca por lo que hace, sino por cómo lo hace. Tiene cualidades visibles, pero también intangibles que consiguen que el equipo brille. A veces nos transmite la sensación de que logra todo por sí mismo, aunque en realidad el éxito siempre es colectivo.\nPodemos pensar en referentes como Steve Jobs, cuyo impacto trasciende la tecnología, o en figuras del deporte como Sergio Scariolo, seleccionador de la Selección Española de Baloncesto. Su palmarés como entrenador es impecable, con grandes éxitos… pero también con fracasos sonoros. ¿Qué marca la diferencia en esos escenarios? Los jugadores. Y ahí está la clave: el éxito de un gran líder depende siempre del equipo. Liderar no es estar por encima del grupo, sino hacerlo brillar.\nEl impacto de un líder en su equipo # Si ocupas un puesto de liderazgo y tienes personas a tu cargo, lo primero que debes preguntarte es:\n¿Tengo el equipo que quiero? ¿Se complementan entre sí? ¿Qué fortalezas y debilidades compartimos? Puede parecer algo básico, pero para mí es lo más importante: un equipo se define por lo que su líder quiere construir de él.\nEl deporte nos ofrece ejemplos muy claros. Pensemos en los Lakers que reunieron a Gary Payton, Kobe Bryant, Karl Malone y Shaquille O’Neal, dirigidos por Phil Jackson, considerado el mejor entrenador de la historia. Sobre el papel parecía un equipo imbatible, pero los resultados fueron un fracaso. ¿Por qué? Porque no se complementaban. Había talento individual, pero no existía cohesión.\nDesde la perspectiva del liderazgo, Phil Jackson no logró encontrar el punto que hiciera funcionar a esas estrellas como conjunto. Tal vez por miedo a perder a alguno de ellos frente a la directiva, no tomó decisiones valientes: reducir minutos, reorganizar quintetos o incluso prescindir de alguno. Esa falta de determinación también fue parte del fracaso.\nAhora bien, culpar solo al líder sería injusto. Los jugadores también fallaron: no supieron adaptarse, no lideraron su rol dentro del equipo y bajaron su rendimiento. El fracaso fue compartido.\nEste ejemplo refleja algo que a veces olvidamos en el día a día: la unión hace al equipo. Puedes rodearte de personas con mucho talento, pero si no trabajan en función de un objetivo común, lo más probable es que no llegues muy lejos.\nEl concepto de Equipazgo # En gestión de proyectos o productos, esto se traduce en algo muy concreto: que el éxito depende de todos. Cada persona debe saber cuáles son sus funciones y estar ubicada en el lugar donde más puede aportar.\nComo dice Manu Fosela en su libro Liderazgo Afectivo, este enfoque se llama Equipazgo: un grupo de profesionales donde cada miembro asume liderazgo en su ámbito, toma decisiones y trabaja en conjunto por un objetivo común.\nPor qué aspiro a trabajar en un Equipazgo # Muchas veces asociamos la figura del líder a alguien casi “superhéroe”, capaz de hacerlo todo. Ese liderazgo personal logra resultados inmediatos, pero suele ser insostenible: cuando el líder se va, los procesos dejan de fluir y el equipo parece perder valor.\nYo mismo lo he vivido en mi transición de técnico a manager: a veces terminas asumiendo tareas que no te corresponden para tapar carencias del equipo. El objetivo se consigue, sí, pero sacrificando tu verdadero rol.\nPor eso creo en el liderazgo compartido. No significa que todos tengan el mismo poder de decisión —eso sería democracia—, pero sí que cada persona lidere su parcela de responsabilidad. Desde el primero hasta el último miembro del equipo. Algo tan simple como testear, documentar bien y pedir feedback ya es una forma de liderazgo.\nEl reto está en que muchos solo hacen “su tarea” y esperan a que alguien más la empuje. Aquí entra el rol del líder: explicar claramente qué espera de cada persona, ser coherente en la exigencia y ayudar a cada uno a mejorar. Siempre digo a mi equipo que su “yo de dentro de un año” debe ser mejor que su “yo actual”. Y esa misma exigencia me la aplico a mí.\nNo todo el mundo disfruta de este enfoque. Hay personas que prefieren quedarse en su zona de confort, y está bien. Pero si alguien no encaja con la cultura y los valores de la compañía, es mejor afrontarlo pronto, por el bien de todos.\nEl Equipazgo no significa pensar igual, caerse bien o estar siempre de acuerdo, sino saber trabajar juntos y complementarse. Si además hay confianza, humor y buen ambiente, mucho mejor, pero eso debe ser una consecuencia, no un objetivo.\nEl reto de sostener un Equipazgo # Lograr un Equipazgo es difícil y mantenerlo en el tiempo aún más. Cada persona es distinta, cambia con el tiempo, y los equipos también evolucionan. Pero cuando ocurre, es una experiencia única y enormemente gratificante.\nSe suele decir que un buen manager se nota cuando el equipo funciona incluso en su ausencia. Estoy de acuerdo, con un matiz: un buen líder no solo asegura el presente, sino que anticipa los problemas del futuro. Si el primer día que no estás ya se nota tu ausencia, probablemente no estabas liderando de la mejor forma.\n👉 Para mí, un verdadero líder no es quien brilla más, sino quien consigue que su equipo brille para conseguir los objetivos propuestos.\n","date":"13 septiembre 2025","externalUrl":null,"permalink":"/posts/equipazgo-lideres/","section":"Posts","summary":"En los últimos meses he estado leyendo más de lo habitual, buscando libros y artículos que me ayuden en mi nuevo rol, mucho más centrado en la gestión de personas. Este cambio me exige desarrollar habilidades diferentes a las que antes dominaba.\n","title":"El impacto del liderazgo y la importancia del equipazgo","type":"posts"},{"content":"","date":"13 septiembre 2025","externalUrl":null,"permalink":"/tags/equipazgo/","section":"Tags","summary":"","title":"Equipazgo","type":"tags"},{"content":"En el mundo del desarrollo de software moderno, la seguridad y el rendimiento no deben ser una ocurrencia tardía, sino pilares fundamentales desde el inicio del proyecto. Ignorar estos aspectos puede llevar a vulnerabilidades críticas y a un deterioro del rendimiento que afecte directamente la experiencia del usuario y, en última instancia, la viabilidad de un producto. Una de las estrategias más efectivas y sencillas para mitigar estos riesgos es la implementación de Rate Limiting. Este artículo es el primero de una serie enfocada en la seguridad, donde exploraremos diversas acciones, tanto a nivel de código como de infraestructura, para proteger nuestras aplicaciones.\nQué es el Rate Limiting y por qué es tan importante? 🔐 # El Rate Limiting, o limitación de tasa, es una práctica de seguridad que controla la cantidad de solicitudes que un cliente puede hacer a un servidor dentro de un período de tiempo determinado.\nLa analogía de un fusible en el hogar es perfecta: cuando el consumo de energía excede un límite predefinido, el fusible salta para evitar daños mayores. En el software, el Rate Limiting actúa de manera similar, protegiendo nuestros recursos de un uso excesivo.\n¿Por qué es crucial para una API o servicio web?\nPrevenir Ataques de Denegación de Servicio (DoS): Un atacante puede saturar un endpoint con miles de solicitudes por segundo, lo que consume recursos de la base de datos y de la CPU, volviendo el servicio inaccesible para los usuarios legítimos. Controlar Costos: Si tu servicio depende de APIs de terceros que cobran por uso, o si tu infraestructura tiene costos asociados al tráfico (como el ancho de banda), el Rate Limiting te ayuda a evitar facturas inesperadas causadas por un uso abusivo. Asegurar la Equidad y la Estabilidad: Garantiza que un usuario o aplicación no acapare todos los recursos disponibles, asegurando una experiencia equitativa y estable para todos los clientes. Es importante recordar que el Rate Limiting no es una solución de seguridad única. Es parte de una estrategia de defensa en profundidad que debe complementarse con otras medidas, como el uso de un WAF (Web Application Firewall), bloqueo de IPs maliciosas y detección de bots.\nImplementando Rate Limiting en .NET 🚀 # Desde .NET 8, el middleware de Rate Limiting es una característica integrada en el framework, lo que simplifica enormemente su implementación. Para versiones anteriores, puedes utilizar el paquete NuGet Microsoft.AspNetCore.RateLimiting.\nLa configuración se realiza en el archivo Program.cs de tu proyecto. El primer paso es añadir el servicio:\nbuilder.Services.AddRateLimiter(options =\u0026gt; { // Aquí se definen las políticas de limitación }); El objeto options de RateLimiterOptionsExtensions nos permite definir varias políticas de limitación.\nTipos de Políticas de Rate Limiting en .NET # El framework ofrece cuatro algoritmos de control principales:\nFixed Window Limiter: Permite un número fijo de solicitudes dentro de una ventana de tiempo. Una vez que la ventana de tiempo se agota, los contadores se reinician. Sliding Window Limiter: Similar al anterior, pero la ventana de tiempo se divide en segmentos. A medida que pasa el tiempo, los segmentos más antiguos se descartan y se abren nuevos, lo que ofrece un control más granular y suave del tráfico. Token Bucket Limiter: El límite se basa en un \u0026ldquo;cubo de tokens\u0026rdquo;. El cubo se llena a una tasa constante y cada solicitud consume un token. Si el cubo está vacío, la solicitud es rechazada. Esto permite ráfagas de tráfico controladas. Concurrency Limiter: Limita el número de solicitudes simultáneas que puede manejar el servidor. Las solicitudes adicionales se ponen en cola o se rechazan. Este método no se basa en un período de tiempo, sino en la capacidad instantánea del sistema. Configuración de las Políticas # Aquí tienes un ejemplo de cómo configurar cada una de estas políticas en tu código:\nbuilder.Services.AddRateLimiter(options =\u0026gt; { // Política de Ventana Fija options.AddFixedWindowLimiter(\u0026#34;FixedPolicy\u0026#34;, opt =\u0026gt; { opt.Window = TimeSpan.FromSeconds(20); opt.PermitLimit = 2; opt.QueueLimit = 0; opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; }); // Política de Ventana Deslizante options.AddSlidingWindowLimiter(\u0026#34;SlidingPolicy\u0026#34;, opt =\u0026gt; { opt.Window = TimeSpan.FromSeconds(20); opt.SegmentsPerWindow = 2; opt.PermitLimit = 2; opt.QueueLimit = 0; opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; }); // Política de Cubo de Tokens options.AddTokenBucketLimiter(\u0026#34;TokenPolicy\u0026#34;, opt =\u0026gt; { opt.TokenLimit = 2; opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; opt.QueueLimit = 0; opt.ReplenishmentPeriod = TimeSpan.FromSeconds(20); opt.TokensPerPeriod = 60; opt.AutoReplenishment = true; }); // Política de Concurrencia options.AddConcurrencyLimiter(\u0026#34;ConcurrencyPolicy\u0026#34;, opt =\u0026gt; { opt.PermitLimit = 10; opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; opt.QueueLimit = 2; }); // Configuración global para rechazos options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; }); Una de las decisiones más importantes es la configuración de la cola de espera (QueueLimit y QueueProcessingOrder). Si bien una cola puede manejar ráfagas de tráfico, también consume recursos de memoria y puede causar latencia en las respuestas. En muchos casos, rechazar la solicitud de inmediato (QueueLimit = 0) es preferible para mantener la estabilidad y evitar que un ataque DoS consuma todos los recursos.\nPor defecto, si una solicitud es rechazada, el middleware devuelve un código de estado 503 (Service Unavailable). Sin embargo, en un contexto de API, es más apropiado devolver un 429 (Too Many Requests) para informar al cliente que ha excedido el límite de llamadas, lo cual se configura con options.RejectionStatusCode.\nAplicando las Políticas a los Endpoints # Una vez que las políticas están definidas, solo queda aplicarlas a los endpoints. En una API Minimal, esto se hace fácilmente con el método RequireRateLimiting().\nprotected override RouteHandlerBuilder Configure(RouteHandlerBuilder builder) =\u0026gt; builder // ... otras configuraciones .WithName(\u0026#34;GetAvenger\u0026#34;) .WithTags(\u0026#34;Avengers\u0026#34;) .RequireRateLimiting(\u0026#34;FixedPolicy\u0026#34;); Aquí, hemos aplicado la política FixedPolicy al endpoint GetAvenger.\nResumen # El Rate Limiting es una medida de seguridad fundamental para proteger las aplicaciones web de un uso excesivo y malicioso. Al implementar esta técnica, prevenimos ataques de denegación de servicio (DoS), controlamos los costos de nuestra infraestructura y garantizamos la estabilidad del servicio. En .NET 8, el middleware integrado facilita su configuración, permitiendo definir diferentes políticas (Fixed, Sliding, Token Bucket y Concurrency) y aplicarlas de manera granular a los endpoints. Esta práctica, combinada con otras medidas de seguridad, es clave para construir productos digitales robustos y rentables.\nPodéis ver el ejemplo en mi repositorio de Github\nHappy Codding\nReferencias # Rate limiting middleware in ASP.NET Core\n","date":"30 agosto 2025","externalUrl":null,"permalink":"/posts/security-developers/","section":"Posts","summary":"En el mundo del desarrollo de software moderno, la seguridad y el rendimiento no deben ser una ocurrencia tardía, sino pilares fundamentales desde el inicio del proyecto. Ignorar estos aspectos puede llevar a vulnerabilidades críticas y a un deterioro del rendimiento que afecte directamente la experiencia del usuario y, en última instancia, la viabilidad de un producto. Una de las estrategias más efectivas y sencillas para mitigar estos riesgos es la implementación de Rate Limiting. ","title":"Rate Limiting en .NET: Protegiendo tu API desde el Principio","type":"posts"},{"content":"","date":"30 agosto 2025","externalUrl":null,"permalink":"/tags/security/","section":"Tags","summary":"","title":"Security","type":"tags"},{"content":"","date":"8 diciembre 2024","externalUrl":null,"permalink":"/tags/hybridcache/","section":"Tags","summary":"","title":"Hybridcache","type":"tags"},{"content":"","date":"8 diciembre 2024","externalUrl":null,"permalink":"/tags/redis/","section":"Tags","summary":"","title":"Redis","type":"tags"},{"content":"Recientemente se ha lanzado .NET 9, que no es una versión de soporte extendido (LTS). Ya compartí mi opinión sobre el tema del versionado y cómo lo gestionamos en ocasiones en el artículo que escribí para CompartiMOSS hace un tiempo. Si me preguntáis si en los proyectos en los que trabajamos vamos a migrar a .NET 9, mi respuesta es un rotundo SÍ.\nAunque muchos saben que dentro de un año llegará .NET 10, una versión con soporte a largo plazo, el soporte adicional que ofrece no siempre justifica el esfuerzo de quedarse en una versión LTS durante más tiempo. En el tiempo que ahorras al evitar actualizar de versión en versión, acabas perdiendo rendimiento y los beneficios de las nuevas características.\nUno de los grandes avances en .NET 9 es la optimización en el uso de memoria. En nuestro caso, al desplegar productos en AKS, el tamaño de los pods en términos de CPU y memoria es un factor crítico. Históricamente, los procesos en .NET tenían un consumo de memoria que crecía de forma constante hasta que se reiniciaban. Sin embargo, gracias a las mejoras en el Garbage Collector, este comportamiento se ha corregido notablemente.\nIntroducción # Sin embargo, hoy no vengo a profundizar en ese tema (lo dejaré para otro artículo), sino a reflexionar sobre otra novedad: HybridCache. Resulta curioso que esta funcionalidad destaque tanto dentro de la comunidad, especialmente cuando es algo que ya venimos implementando desde .NET 3. Para nosotros, no es un argumento suficientemente fuerte para justificar la actualización de versión, lo cual me lleva a preguntarme si los desarrolladores realmente conocen las entrañas de .NET.\n¿Qué es el patrón Cache? # El patrón Cache es una técnica de diseño que optimiza el acceso a recursos costosos de calcular o recuperar, almacenándolos temporalmente en una memoria rápida. Esto mejora el rendimiento y reduce la latencia de las aplicaciones. Sus principales beneficios son:\nReducir la latencia: Evitar operaciones repetitivas como consultas a bases de datos o llamadas a APIs. Mejorar el rendimiento: Proporcionar acceso rápido a datos previamente procesados o recuperados. Optimizar recursos: Reducir el uso innecesario de CPU, ancho de banda o almacenamiento. Implementación en .NET # .NET ofrece varias opciones para implementar una caché. Por ejemplo, con IMemoryCache, los datos se almacenan directamente en la memoria de la aplicación. En cambio, al usar la interfaz IDistributedCache, los datos se guardan en un sistema externo como Redis o SQL. Podéis explorar más sobre estas opciones en la documentación oficial.\nCacheService: Una solución personalizada # En nuestros desarrollos, utilizamos una librería propia para implementar una arquitectura de datos poliglota que maximice la eficiencia económica y técnica. Basándonos en una implementación previa de Fernando Escolar y Jorge Turrado, surgió CacheService, una solución Open Source compatible desde .NET 7.\nConfiguración de CacheService # Para instalar CacheService, basta con ejecutar:\ndotnet add package CacheService Asegúrate de que las dependencias necesarias estén registradas en tu aplicación:\nservices.AddLogging(); services.AddMemoryCache(); services.AddStackExchangeRedisCache(op =\u0026gt; ...); services.AddCacheService(); Una vez configurado, puedes integrarlo en una Minimal API:\npublic record struct Request( [FromServices] AvengerDbContext Database, [FromServices] ICacheService Cache, [FromQuery] int Page = 1, [FromQuery] int PageSize = 10 ); protected override async Task\u0026lt;IResult\u0026gt; HandleAsync(Request req, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var total = await req.Database.Avengers.CountAsync(cancellationToken); if (total == 0) return Results.NoContent(); var avengers = await req.Cache.GetOrSetAsync( $\u0026#34;avengers\u0026#34;, async token =\u0026gt; await req.Database.Avengers .Select(b =\u0026gt; new ResponseItem { Id = b.Id, Name = b.Name, Photo = b.Photo, Strength = b.Strength }) .OrderBy(b =\u0026gt; b.Name) .Skip((req.Page - 1) * req.PageSize) .Take(req.PageSize) .ToListAsync(cancellationToken), cancellationToken: cancellationToken ); return Results.Ok(new Response(avengers, total, req.Page, req.PageSize)); } HybridCache: ¿Qué aporta? # HybridCache se instala mediante:\ndotnet add package Microsoft.Extensions.Caching.Hybrid Proporciona opciones avanzadas como límites de tamaño de los elementos en caché y configuraciones específicas de expiración:\nbuilder.Services.AddHybridCache(options =\u0026gt; { options.MaximumPayloadBytes = 10 * 1024 * 1024; // 10 MB options.MaximumKeyLength = 512; options.DefaultEntryOptions = new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(30), LocalCacheExpiration = TimeSpan.FromMinutes(30) }; }); Ejemplo de uso con HybridCache # Al igual que con CacheService, puedes integrarlo fácilmente:\npublic record struct Request( [FromServices] AvengerDbContext Database, [FromServices] Microsoft.Extensions.Caching.Hybrid.HybridCache Cache, [FromQuery] int Page = 1, [FromQuery] int PageSize = 10 ); protected override async Task\u0026lt;IResult\u0026gt; HandleAsync(Request req, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var total = await req.Database.Avengers.CountAsync(cancellationToken); if (total == 0) return Results.NoContent(); var avengers = await req.Cache.GetOrCreateAsync( $\u0026#34;avengers\u0026#34;, async token =\u0026gt; await req.Database.Avengers .Select(b =\u0026gt; new ResponseItem { Id = b.Id, Name = b.Name, Photo = b.Photo, Strength = b.Strength }) .OrderBy(b =\u0026gt; b.Name) .Skip((req.Page - 1) * req.PageSize) .Take(req.PageSize) .ToListAsync(cancellationToken), cancellationToken: cancellationToken ); return Results.Ok(new Response(avengers, total, req.Page, req.PageSize)); } Resumen # Este artículo no busca explicar en detalle el funcionamiento de HybridCache, sino fomentar el pensamiento crítico sobre nuestras elecciones tecnológicas. Aunque HybridCache puede ser útil, en nuestro caso no aporta beneficios adicionales frente a CacheService.\n¿Vosotros qué opináis? ¡Os leo!\nHappy coding\n","date":"8 diciembre 2024","externalUrl":null,"permalink":"/posts/dotnet9hybridcache/","section":"Posts","summary":"Recientemente se ha lanzado .NET 9, que no es una versión de soporte extendido (LTS). Ya compartí mi opinión sobre el tema del versionado y cómo lo gestionamos en ocasiones en el artículo que escribí para CompartiMOSS hace un tiempo. Si me preguntáis si en los proyectos en los que trabajamos vamos a migrar a .NET 9, mi respuesta es un rotundo SÍ.\n","title":"Sobre HybridCache y cómo sacar más partido a DotNet","type":"posts"},{"content":"","date":"22 octubre 2024","externalUrl":null,"permalink":"/categories/charlas/","section":"Categories","summary":"","title":"Charlas","type":"categories"},{"content":"","date":"22 octubre 2024","externalUrl":null,"permalink":"/tags/charlas/","section":"Tags","summary":"","title":"Charlas","type":"tags"},{"content":"","date":"22 octubre 2024","externalUrl":null,"permalink":"/tags/conferencias/","section":"Tags","summary":"","title":"Conferencias","type":"tags"},{"content":"¡La NetCoreConf 2024 en Madrid fue todo un éxito! Este evento, celebrado en las oficinas de Microsoft en Pozuelo de Alarcón, destacó por ser un espacio donde la comunidad tecnológica se reunió para aprender, compartir y conectar. Con una agenda variada que incluyó temas como Power BI \u0026amp; Fabric, .NET Aspire, Inteligencia Artificial, Big Data, y Unity Catalog, el evento combinó charlas inspiradoras con talleres prácticos.\nAdemás de las charlas, hubo espacio para networking, con muchas risas y colaboración entre profesionales, destacando el apoyo constante de la comunidad #WomenInTech y el entusiasmo por aprender y compartir conocimientos.\nEl evento dejó un impacto positivo, generando expectativas para la próxima edición en 2025 y fortaleciendo la comunidad tecnológica. ¡Sin duda, un encuentro inolvidable para quienes asistieron! 🎉\n","date":"22 octubre 2024","externalUrl":null,"permalink":"/posts/netcoreconf-madrid-2024/","section":"Posts","summary":"¡La NetCoreConf 2024 en Madrid fue todo un éxito! Este evento, celebrado en las oficinas de Microsoft en Pozuelo de Alarcón, destacó por ser un espacio donde la comunidad tecnológica se reunió para aprender, compartir y conectar. Con una agenda variada que incluyó temas como Power BI \u0026 Fabric, .NET Aspire, Inteligencia Artificial, Big Data, y Unity Catalog, el evento combinó charlas inspiradoras con talleres prácticos.\n","title":"NetCoreConf Madrid 2024","type":"posts"},{"content":"A lo largo de mi trayectoria profesional, siempre he sido y sigo siendo un firme defensor de las buenas prácticas de programación, esforzándome para que los proyectos en los que participo sean motivo de orgullo. Sin embargo, quienes llevamos tiempo en proyectos sabemos que, por muy bueno que sea alguien o por mucho conocimiento que tenga, no puede hacer que un proyecto sea excelente por sí solo. Todo ese conocimiento debe compartirse entre los miembros del equipo para que puedan aplicarlo y así evitar cometer errores. ¿Cómo se puede lograr esto? Hace tiempo, era un firme defensor de herramientas de análisis estático de código, como SonarQube o Kiwan. Estas herramientas analizaban el código una vez que lo subías a la rama y te mostraban sugerencias/recomendaciones para mejorarlo. No obstante, hay aspectos que no terminan de encajar en los equipos. El primero es el tiempo: tienes que subir tu código y luego consultar qué cosas puedes mejorar. ¿Qué ocurría la mayoría de las veces? Que, al final, los últimos días del proyecto se dedicaban a \u0026ldquo;solucionar\u0026rdquo; lo que, según estas herramientas, estaba mal, pero el objetivo no es solo corregir, sino aprender. Y cuando el equipo lo hace de esta manera en el último día, no se aprende realmente (es como cuando estudiabas todo la noche antes de un examen, ¿sinceramente, te acordabas de lo que habías estudiado una semana después?). Además, hay otro motivo por el cual actualmente tampoco confío tanto en estas herramientas: además de ser un análisis sintáctico, el código tiene un contexto, un \u0026ldquo;por qué\u0026rdquo;. A veces, haces las cosas de cierta forma porque lo consideras necesario. Un ejemplo trivial: si hacemos un proyecto con una arquitectura Vertical Slice, independientemente de si el equipo lo hace mejor o peor, la herramienta probablemente señalará que hay código repetido o que se podría refactorizar. Pero, según Vertical Slice, cada \u0026ldquo;feature\u0026rdquo; debe tener todo lo necesario para ser independiente de las otras, y no podemos reutilizar una clase modelo (DTO), aunque la base sea la misma (se podría, pero entonces no lo llamaríamos Vertical Slice aunque esa conversación daría para otro post). Está claro que este es un ejemplo muy obvio, pero puede haber decisiones que se toman debido a la casuística de ese desarrollo, y aunque no sean la mejor opción desde el punto de vista estático, sí lo son para cumplir con los requisitos de la aplicación. Además, he visto proyectos con una puntuación \u0026ldquo;A\u0026rdquo; en Sonar que eran, sinceramente, de los más chapuceros que he visto, mientras que otros con una \u0026ldquo;C\u0026rdquo; mostraban un rendimiento espectacular gracias a las decisiones que se tomaron.\nTambién hay otro punto de punto de vista que es como ve el equipo la incoporación de estas herramientas. Muchos equipos lo ven como una herramienta de control, de supervisión y ante tal perspectiva un equipo no la adopta de una forma ni eficiente ni eficaz, el equipo tiene que llegar a un grado de madurez en el que sea consciente de que esta herramienta su objetivo principal es el de incrementar el valor de lo que se esta desarrollando/entregando sino da igual lo buena que sea la herramienta o opción que el equipo de desarrollo no la va utilizar. Así que como consejo de alguíen que se ha equivocado muchas veces antes de decidir que quieres poner establece un plan para la adopción de dicha herramienta.\nRoslyn # Todo lo comentado anteriormente corresponde a la época de .NET Framework. Con la reescritura del framework para mejorar su rendimiento, hacerlo multiplataforma y posicionarlo como uno de los lenguajes más utilizados en las empresas, se realizaron muchos cambios, y uno de ellos fue Roslyn. Para quienes no lo conozcan, Roslyn es el nombre en clave del proyecto de la plataforma de compiladores de .NET, que toma su nombre de una ciudad homónima en Washington, EE.UU. ¿Qué hace especial a Roslyn en comparación con el compilador anterior?\nEs un proyecto open source, lo que significa que cualquier persona puede crear su propia \u0026ldquo;versión\u0026rdquo; de C# y adaptar los requisitos a sus necesidades. Es más que un compilador; es un conjunto de herramientas y compiladores que, combinados, otorgan al lenguaje una potencia espectacular. Te muestra el resultado en tiempo real (recordemos que uno de los mayores problemas de las herramientas de análisis de código estático es que los resultados se obtenían a posteriori). Es multiplataforma, lo que permite su uso en cualquier sistema operativo. Al final, esta opción no quedó relegada solo a quienes quisieran modificar algunas reglas de compilación. Sin embargo, su verdadero valor no se percibió de inmediato.\nCon Roslyn, las empresas que se dedican a analizar código en C# han empezado a priorizar la creación de analizadores que nos ayuden a mejorar nuestro código. Cuando hablo de mejorar, no me refiero solo a reducir el número de líneas de código, sino a optimizar el rendimiento, permitir que el equipo aprenda de sus errores, los solucione y evolucione. Por eso, no hay mejor alternativa que ofrecer al desarrollador advertencias o mensajes críticos junto con una referencia que explique por qué la propuesta es mejor. Con todas estas virtudes, considero esencial tener estas herramientas dentro de los equipos.\nMucha gente dirá que para eso están las revisiones de Pull Request. Mi opinión personal es que una Pull Request debe validar que se ha cumplido con lo requerido; discutir si una variable va en mayúsculas, se define con var, con un _, o según el estándar acordado, es algo que una máquina puede hacer por nosotros. Así, podemos centrarnos en lo que realmente aporta valor y beneficio al proyecto.\n¿Donde obtenemos los Analyzers? # Naturalmente, todo lo que encontramos en el ecosistema de .NET se puede obtener desde NuGet. Si buscamos cuáles son los más descargados, podemos hacernos una idea de las preferencias de la comunidad:\nColecciones de Analizadores # El propio compilador oficial de Roslyn ofrece una serie de analizadores que podemos utilizar: Microsoft.CodeAnalysis.NetAnalyzers, Microsoft.CodeAnalysis.BannedApiAnalyzers, Microsoft.CodeAnalysis.PublicApiAnalyzers, Microsoft.CodeAnalysis.Analyzers, Roslyn.Diagnostics.Analyzers. StyleCop tampoco se ha quedado atrás y también ha publicado una serie de analizadores que podemos encontrar en StyleCopAnalyzers. SonarLint, continuando con la evolución de sus reglas anteriores, ofrece su paquete de analizadores. BlowinCleanCode es un paquete bastante interesante que se enfoca en el principio de responsabilidad, la encapsulación y las buenas prácticas: BlowinCleanCode. Seguridad # La seguridad es un aspecto crucial que debemos tener en cuenta, y es importante identificar posibles vulnerabilidades en nuestro código. SecurityCodeScan es una opción que aborda estas necesidades. PumaSecurity es un analizador de seguridad que ofrece un análisis continuo y en tiempo real del código fuente de aplicaciones C#. ASP.NET Core y Web # AngleSharp es una biblioteca de análisis sintáctico que maneja HTML5, CSS3 y XML para construir un DOM basado en la especificación oficial del W3C. AspNetCoreAnalyzers proporciona analizadores específicos para Microsoft.AspNetCore. Async/Multithread # Uno de los grandes desafíos de los equipos es el uso correcto de la asincronía y el multihilo. Muchas fugas de memoria y cuellos de botella en las aplicaciones son consecuencia de un mal uso de estos conceptos. Estos analizadores pueden ayudarte a encontrar soluciones \u0026ldquo;mágicas\u0026rdquo; a algunos problemas de tus desarrollos:\nAsyncFixer: Diagnósticos avanzados de Async/Await y CodeFixes para C#. SmartAnalyzers.MultithreadingAnalyzer: Diagnósticos avanzados de multihilos y CodeFixes para C#. Bibliotecas de terceros # Algunas de las bibliotecas más \u0026ldquo;utilizadas/famosas\u0026rdquo; también han añadido analizadores para garantizar su buen uso. Librerías como Moq, xUnit y nSubstitute ofrecen esta ayuda para asegurar el correcto uso de sus funcionalidades.\nComo estandarizamos la forma de trabajar de los equipos. # Los analizadores que hemos mencionado anteriormente no deben incluirse todos en tu proyecto, sino pobre del IDE y el tiempo de compilación del mismo. Lo importante es seleccionar aquellos que se consideren más adecuados para el equipo. Hay que tener en cuenta que, cuántos más analizadores se utilicen, más probable es que surjan contradicciones entre ellos. Es fundamental abordar los aspectos más importantes del desarrollo y luego decidir cuáles son los analizadores más óptimos para tu equipo. Por lo general, la práctica común es instalar el paquete NuGet en cada proyecto de la solución. Sin embargo, esto puede ser un poco tedioso, especialmente si se busca que todos los equipos de tu organización sigan las mismas pautas. ¿Cómo podemos estandarizar esto? Con los cambios introducidos, Microsoft ha lanzado una utilidad llamada Build.props, que es un archivo destinado a estandarizar lo que llevarán todos los proyectos de una solución. Este archivo es una excelente solución para unificar el naming en todos los proyectos, establecer bibliotecas comunes e instalar los analizadores que queremos usar. De esta manera, podemos crear una \u0026ldquo;plantilla\u0026rdquo; para aplicarla en todos los proyectos y equipos. Si no estás utilizando esta herramienta, te recomiendo leer la documentación oficial para su uso.\nResumen # En resumen, la clave para asegurar la calidad en nuestros proyectos no radica únicamente en utilizar herramientas de análisis estático o seguir al pie de la letra las reglas que estas imponen. Es esencial comprender el contexto de cada decisión de desarrollo y fomentar un aprendizaje continuo dentro del equipo. Herramientas como Roslyn y los analizadores específicos que seleccionemos pueden ser de gran ayuda, pero siempre deben ser complementadas con un enfoque humano que valore el criterio profesional y la colaboración. Así, podremos lograr no solo un código limpio, sino también un equipo que evoluciona y mejora con cada proyecto.\nY tú que analyzers usas? Te parece una buena opción?\nHappy codding\n","date":"11 septiembre 2024","externalUrl":null,"permalink":"/posts/analyzers-csharp/","section":"Posts","summary":"A lo largo de mi trayectoria profesional, siempre he sido y sigo siendo un firme defensor de las buenas prácticas de programación, esforzándome para que los proyectos en los que participo sean motivo de orgullo. Sin embargo, quienes llevamos tiempo en proyectos sabemos que, por muy bueno que sea alguien o por mucho conocimiento que tenga, no puede hacer que un proyecto sea excelente por sí solo. Todo ese conocimiento debe compartirse entre los miembros del equipo para que puedan aplicarlo y así evitar cometer errores. ¿Cómo se puede lograr esto? Hace tiempo, era un firme defensor de herramientas de análisis estático de código, como SonarQube o Kiwan. Estas herramientas analizaban el código una vez que lo subías a la rama y te mostraban sugerencias/recomendaciones para mejorarlo. No obstante, hay aspectos que no terminan de encajar en los equipos. El primero es el tiempo: tienes que subir tu código y luego consultar qué cosas puedes mejorar. ¿Qué ocurría la mayoría de las veces? Que, al final, los últimos días del proyecto se dedicaban a “solucionar” lo que, según estas herramientas, estaba mal, pero el objetivo no es solo corregir, sino aprender. Y cuando el equipo lo hace de esta manera en el último día, no se aprende realmente (es como cuando estudiabas todo la noche antes de un examen, ¿sinceramente, te acordabas de lo que habías estudiado una semana después?). Además, hay otro motivo por el cual actualmente tampoco confío tanto en estas herramientas: además de ser un análisis sintáctico, el código tiene un contexto, un “por qué”. A veces, haces las cosas de cierta forma porque lo consideras necesario. Un ejemplo trivial: si hacemos un proyecto con una arquitectura Vertical Slice, independientemente de si el equipo lo hace mejor o peor, la herramienta probablemente señalará que hay código repetido o que se podría refactorizar. Pero, según Vertical Slice, cada “feature” debe tener todo lo necesario para ser independiente de las otras, y no podemos reutilizar una clase modelo (DTO), aunque la base sea la misma (se podría, pero entonces no lo llamaríamos Vertical Slice aunque esa conversación daría para otro post). Está claro que este es un ejemplo muy obvio, pero puede haber decisiones que se toman debido a la casuística de ese desarrollo, y aunque no sean la mejor opción desde el punto de vista estático, sí lo son para cumplir con los requisitos de la aplicación. Además, he visto proyectos con una puntuación “A” en Sonar que eran, sinceramente, de los más chapuceros que he visto, mientras que otros con una “C” mostraban un rendimiento espectacular gracias a las decisiones que se tomaron.\n","title":"Analizando tu código sin morir en el intento","type":"posts"},{"content":"","date":"11 septiembre 2024","externalUrl":null,"permalink":"/tags/analyzers/","section":"Tags","summary":"","title":"Analyzers","type":"tags"},{"content":"","date":"11 septiembre 2024","externalUrl":null,"permalink":"/tags/sonar/","section":"Tags","summary":"","title":"Sonar","type":"tags"},{"content":"","date":"17 marzo 2024","externalUrl":null,"permalink":"/tags/azure/","section":"Tags","summary":"","title":"Azure","type":"tags"},{"content":"","date":"17 marzo 2024","externalUrl":null,"permalink":"/tags/base-de-datos/","section":"Tags","summary":"","title":"Base De Datos","type":"tags"},{"content":"","date":"17 marzo 2024","externalUrl":null,"permalink":"/tags/cosmos/","section":"Tags","summary":"","title":"Cosmos","type":"tags"},{"content":"Quizás uno de los servicios más caros que ofrece Azure sea Cosmos DB, un servicio que reúne diversas bases de datos no relacionales con una multitud de características que la convierten en una de las mejores del mercado. Sin embargo, ¿realmente es tan caro para implementarlo en todos los proyectos, o quizás no lo es tanto? En este artículo, intentaremos ofrecer una serie de consejos para comprender mejor todo lo que Azure nos ofrece y así poder pagar lo necesario en nuestro desarrollo.\n¿Por qué se nos cobra en Cosmos DB? # Las Bases de Datos de Cosmos se facturan en función de lo que conocemos como Request Unit, o RU en adelante. Las RU representan el costo de los recursos de procesamiento necesarios para ejecutar operaciones en la base de datos, como leer, escribir y actualizar datos. Cada operación que se realiza consume una cierta cantidad de RU. Naturalmente, no todas las operaciones cuestan lo mismo; por ejemplo, una operación de lectura del primer elemento costará menos RU que una actualización de un registro completo. Estas RU son utilizadas para dimensionar y administrar la capacidad de rendimiento de nuestra base de datos. Por lo tanto, es crucial comprender su importancia al elegir el modo de capacidad de la base de datos, así como si deseamos ajustar el Throughput compartido entre todas las colecciones de la base de datos o si cada colección debe tener su provisión de forma independiente.\nEl primer paso y elección es indicar si vamos a tener un rendimiento aprovisionado o Serverless. Aunque la pregunta parece sencilla, no lo es tanto. Generalmente, todo lo que tenemos en la nube lo queremos en modo Serverless; no queremos administrar nada del servidor, ni preocuparnos por nada, y que nos cobren por lo que estamos utilizando. ¿No es ese el sueño perfecto? A nivel de marketing, suena muy bien, pero siempre conviene leer las características del servicio. El modo Serverless no asigna una capacidad de RU, lo que significa que si experimentamos una carga de trabajo imprevisible, nuestra base de datos puede quedarse sin recursos para ejecutarla. Además, el servicio está vinculado a una única región de Azure, lo que significa que si esa región falla, también quedaremos sin servicio. El rendimiento de la base de datos en este modo también tiene un valor más elevado en las escrituras que el modo normal. ¿Cuál es la conclusión? No deberíamos usar el modo Serverless. Ni siquiera en entornos no productivos. Puedo enumerar muchas razones:\nDesde un punto de vista técnico, la base de datos no se comporta igual que en un entorno productivo, por lo que asegurar su comportamiento en producción no es algo en lo que pueda confiarse. Desde un punto de vista del producto, no vamos a ahorrar costes. Basta con hacer bien el ejercicio siguiente para ajustar las RU y adaptarlas a nuestras necesidades, y aquí radica la clave del coste. NOTA: Seguramente a muchos les haya pasado como a mí, que este modo les ha resultado una pequeña decepción. Pero hay que pensar que al final, Microsoft es una empresa que quiere ganar la mayor cantidad de dinero posible, y si hiciera este trabajo, probablemente los especialistas como tú y yo nos quedaríamos sin empleo. Así que tomémoslo como una oportunidad en lugar de una crítica.\n¿Qué tipo de Throughput elegimos? # El tipo de escalado que elijamos dependerá principalmente de nuestro desarrollo. Dependiendo del mismo, elegir una opción u otra será la mejor alternativa. Es importante tener en cuenta que una vez elegido el nivel de RU, ya sea a nivel de Base de Datos o de colección, no se puede modificar, a menos que volvamos a crear la base de datos (con todo lo que ello implica, como migración de datos, posible parada del servicio, etc.).\nPara ello, es importante tener en cuenta que el valor mínimo que podemos establecer para una colección son 400 RU. Esto es crucial porque, dependiendo de cómo se utilice esta colección, podemos estar pagando un importe que realmente no estemos consumiendo. Tomemos un ejemplo: imaginemos que tenemos una base de datos para un blog con la siguiente separación:\n\u0026ldquo;articulos\u0026rdquo;: donde almacenaremos todo lo que escribamos. \u0026ldquo;preguntas\u0026rdquo;: una colección donde guardaremos las preguntas que nos hagan los lectores o el feedback recogido en el mismo. ¿Cómo estableceríamos el Throughput? En primer lugar, está claro que los artículos y las RU que consumiremos dependerán de cuántos escribamos a lo largo del año y del número de visitas que pueda tener este post. Supongamos que esta colección tiene un tráfico \u0026ldquo;elevado\u0026rdquo; en el momento en que publicamos el artículo, mientras que el resto de días el tráfico es más bien bajo. Pensemos que, como máximo, tendremos 800 RU el día de más tráfico, mientras que el resto de días tendremos un tráfico de 400 RU.\nPor otro lado, la colección de preguntas es algo que no podemos prever de antemano. ¿Cuánta gente la va a utilizar? Por lo general, recibimos mucho feedback, pero pensemos en un escenario conservador donde no lo recibamos tanto. Así que podríamos tener un tráfico de inserción en la colección de preguntas de una vez a la semana, lo que se traduciría en 50 RU.\nDicho esto, si hacemos los cálculos y distribuimos las RU por colección, dado que el mínimo son 400, estaríamos pagando por dos colecciones: una con un aprovisionamiento autoescalado con un máximo de 1000 RU y otra con un escalado manual de 400 RU. Con esto, estaríamos pagando aproximadamente 850 RU al mes. Ahora bien, ¿cuánto nos costaría si lo hiciéramos a nivel de Base de Datos? En este escenario, pondríamos un autoescalado con un máximo de 1000 RU, que es el mínimo que podemos establecer. Pero esto, al igual que a nivel de colección, implica que tiene un mínimo del 10% de lo que estamos consumiendo. Dado que no podemos establecer 100 RU, estaríamos pagando continuamente 400 RU, y cuando hubiera un pico, escalaría hasta un máximo de 1000 RU, que es el valor que hemos estimado. Por lo tanto, ¿cuál sería el máximo que pagaríamos? Pagaríamos un máximo de 450 RU.\nCon este ejemplo, hemos visto que una buena elección en la distribución de las RU puede provocar un ahorro cercano al 50% en nuestra factura.\n¿Es oro todo lo que reluce? # He presentado un ejemplo y un escenario bastante sencillo y favorable donde la elección es clara. Pero vamos a complicar un poco el ejemplo. Supongamos que nuestro blog está triunfando y aumentamos nuestro tráfico, duplicando la cantidad de visitas, lo que también provoca un aumento en el uso de la funcionalidad de preguntas. ¿Qué puede ocurrir en nuestro desarrollo según lo que hayamos elegido?\nEn el caso de la separación del Throughput por Base de Datos, en la colección de artículos no sería necesario aumentar nada, ya que tenemos el autoescalado activo, lo que significa que al usar más RU, es posible que se nos facture más. Ahora, pensemos en la colección de preguntas. En este caso, al usarse más, es probable que ya esté utilizando adecuadamente los RU y que ambas colecciones estén adaptadas a su uso y proporcionando un servicio justo.\nSin embargo, en el segundo escenario, al poner el autoescalado manual, si el tráfico aumenta en ambas colecciones, el autoescalado aumentaría hasta el máximo que hemos establecido. Sin embargo, ahora es posible que tengamos puntos en los que los RU máximos que tenemos compartidos entre ambas colecciones sean superiores a los que hemos contratado. Esto puede provocar errores en el cliente dependiendo de cómo se comporte.\n¿Esto significa que siempre deberíamos decantarnos por el escalado por colección? La respuesta obvia es que no. Para solucionar este caso, \u0026ldquo;fácilmente\u0026rdquo; podemos aumentar el rango del autoescalado en la Base de Datos para poder digerir esta carga de tráfico. Pero claro, ¿nos costará más dinero, no? No necesariamente. Al tener un autoescalado, es posible que durante algunos momentos estemos un poco por encima de esos 800 RU, pero también tendremos muchos momentos en los que estaremos por debajo de eso, por lo que prácticamente el importe será el mismo en uno u otro escenario.\nEntonces, ¿siempre elegimos el escalado por Base de Datos? Los que me conocen ya saben la respuesta, y es que no. Es la opción más probable y que mejor se adapta a todos los escenarios, pero eso no implica que no puedas encontrarte en situaciones en las que una colección siempre demande un cierto número de RU de forma constante, lo que implica que cualquier cambio en el tráfico en el resto de colecciones pueda afectar negativamente a tu aplicación. ¿Cuándo puede ocurrir esto? Imaginemos que tenemos un producto con 100 millones de clientes. Así que piensa que puede ser un escenario algo diferente y en el que se presentan muchas casuísticas, y donde cualquier decisión que se tome tendrá un impacto en el producto, ya sea en su coste o en la experiencia del cliente.\nResumen # Cosmos DB es uno de los servicios más potentes de Azure, quizás el más caro, pero ofrece características que ningún otro servicio tiene. A menudo, incorporamos muchas funcionalidades que no necesitamos sin pensar por qué hacemos una cosa u otra. El coste que tenemos en nuestro producto es algo que siempre debemos mirar y adecuar a las necesidades que tiene nuestro producto en cada momento. Tenerlo siempre dimensionado según lo que estamos usando es algo que debemos tener en cuenta.\nEste artículo no habría sido posible sin el conocimiento de Fernando Escolar y Almudena Vivanco para llevarlo a la práctica.\n","date":"17 marzo 2024","externalUrl":null,"permalink":"/posts/scale-cosmos/","section":"Posts","summary":"Quizás uno de los servicios más caros que ofrece Azure sea Cosmos DB, un servicio que reúne diversas bases de datos no relacionales con una multitud de características que la convierten en una de las mejores del mercado. Sin embargo, ¿realmente es tan caro para implementarlo en todos los proyectos, o quizás no lo es tanto? En este artículo, intentaremos ofrecer una serie de consejos para comprender mejor todo lo que Azure nos ofrece y así poder pagar lo necesario en nuestro desarrollo.\n","title":"Optimizando el coste de nuestro Cosmos","type":"posts"},{"content":"Otro año más ya llevamos 5 ediciones, un poco este año es como pasar la reválida. Primer año en el que ya no se habló de covid, de medidas sanitarias\u0026hellip; un poco la vuelta a la normalidad. Esta edición la organización en la que yo pertenezco decidimos unos pequeños cambios, queremos potenciar lo que para nosotros tiene que ser un evento de comunidad: que los asistentes se lo pasen pipa, que los speakers tengan ganas de venir a nuestros eventos y tengan implicación con el evento y por último y no menos importante que los partners nos vean un evento atractivo y quieran participar. Eso es un poco la esencia de la NetCoreConf ser un poco diferentes al resto y no queríamos perder se sello. Si todos los asistentes/speakers/partner os habéis sentido bien, eso significa que hemos conseguido nuestro objetivo.\nA nivel de la charla, dado de que uno de los speakers seleccionados no pudo venir, mi hermano Robert Bermejo y yo nos animamos a dar una charla sobre gRPC vs REST. El hilo de esta charla fue que los asistentes pudieran pensar cuás es la mejor solución para el problema que tienen entre manos, unas veces nos valdrá una API Rest, otra gRPC, lo interesante es que conozcamos sus características y cómo podemos aprovecharla en nuestro desarrollo.\nPodeis ver la presentación en el siguiente enlace y también el código fuente que mostramos como ejemplo en el siguiente repo de GitHub.\nCualquier duda sobre el mismo ya sabéis donde estoy y me podéis preguntar, una base de esa charla fue el anterior artículo que escribí, pero hablamos sobre algunos aspectos más avanzados, como performance, rendimiento, cancelation tokens, seguridad, etc.… seguro que me quedará algún artículo para que todo esto no se pierda.\nPor cosas de la tecnología solamente se grabó el track 1 que lo podéis ver aquí.\n","date":"14 marzo 2023","externalUrl":null,"permalink":"/posts/netcoreconf-barcelona-2023/","section":"Posts","summary":"Otro año más ya llevamos 5 ediciones, un poco este año es como pasar la reválida. Primer año en el que ya no se habló de covid, de medidas sanitarias… un poco la vuelta a la normalidad. Esta edición la organización en la que yo pertenezco decidimos unos pequeños cambios, queremos potenciar lo que para nosotros tiene que ser un evento de comunidad: que los asistentes se lo pasen pipa, que los speakers tengan ganas de venir a nuestros eventos y tengan implicación con el evento y por último y no menos importante que los partners nos vean un evento atractivo y quieran participar. Eso es un poco la esencia de la NetCoreConf ser un poco diferentes al resto y no queríamos perder se sello. Si todos los asistentes/speakers/partner os habéis sentido bien, eso significa que hemos conseguido nuestro objetivo.\n","title":"NetCoreConf Barcelona 2023","type":"posts"},{"content":"gRPC es un mecanismo moderno de invocación de métodos remotos (RPC) entre procesos/sistemas/servicios, desarrollado por Google. Utiliza como transporte HTTP/2 y Protocol Buffers como lenguaje de descripción de interfaz\n¿Cuáles son sus características? # El transporte se realiza sobre HTTP/S. Hace uso de Protobuff como interfaz de descripción de los mensajes. Protobuff es neutral y se usa para definir los mensajes y recibidos por un servicio gRPC. Proporciona autenticación, streaming bidireccional y control de flujo. Permite generar clientes multiplataformas y su uso más común es entre microservicios.\n¿Cuáles son sus fortalezas? # Marco de RPC moderno, ligero y de alto rendimiento. Uso reducido de red con serialización binaria Protobuf. Además de multiplexación de varias llamadas sobre una única conexión TCP. Esto para lo que hemos sufrido la falta de sockets disponibles en una WebApp es una bendición. Todos los frameworks para gRPC proveen soporte para generación de código. Esto evita que creemos librerías para consumir dichos servicios y podamos ahorrar tiempo de ejecución. Soporte para streaming a través de HTTP/S gRPC permite a los clientes especificar cuánto están dispuesto para que se complete la solicitud, el tiempo límite se envía al servidor y este decide que acción tomar si excede el tiempo. Seguridad del envió de los mensajes, a diferencia de REST los mensajes gGRPC no son legibles por humanos. ¿Cuáles son sus debilidades? # Hoy su mayor debilidad es que hay un soporte limitado en los navegadores. Los navegadores no poseen el nivel de control necesario para solicitudes que un cliente gRPC requiere. No hay forma de forzar el uso de HTTP/2 y las tramas HTTP/2 son inaccesibles al navegador.\nPor donde empezamos # Lo primero que tenemos que tener en cuenta es definir el contrato y los mensajes que se va a intercambiar entre el cliente y el servidor gRPC. Toda esta definición se realizara en un fichero que tiene una extensión .proto. Por ejemplo, si queremos crear un servicio de Avengers por ejemplo puede tener la siguiente nomenclatura\nsyntax = \u0026#34;proto3\u0026#34;; import \u0026#34;google/protobuf/empty.proto\u0026#34;; option csharp_namespace = \u0026#34;GrpcAvenger\u0026#34;; service Avenger { rpc GetAll (google.protobuf.Empty) returns (GetAllReply); rpc Get (IdAvenger) returns (GetReply); } message IdAvenger { string name = 1; } message AvengerCharacter { string id = 1; string name = 2; string photo = 3; } message GetAllReply { repeated AvengerCharacter name = 1; } message GetReply { AvengerCharacter message = 1; } Del fichero tengamos en cuenta un par de consideraciones en primer lugar hay una serie de tipos de Protobuf que tienen su equivalente en C#. Podemos consultarlo en el siguiente link. Si os dais cuenta cuando estamos pasando un método en blanco el objeto de Google Empty. Posteriormente para poder hacer un array de C# hay que usar la palabra repetead. Luego para crear una clase podemos decir que es similar al contenido que tenemos en el message. Si queréis alguna guía de estilo de cómo se deben definir podéis consultar el siguiente enlace.\nCreando el Server # Una vez ya tenemos definido el Proto, vamos a crear nuestro servidor para ellos nos crearemos un proyecto ASP.NET Core vacío. Se que existen plantillas que ya te crean todo lo necesario para empezar a usar gRPC, sin embargo, para una primera toma de contacto con la tecnología prefiero que entendamos todo lo que hay y dejemos la magia que hace el template para más adelante.\ndotnet new web Una vez el proyecto esta creado, añadimos los siguientes paquetes de Nuget\ndotnet add package Google.Protobuf dotnet add package Grpc.Tools dotnet add package Grpc.AspNetCore De los nugets, por un lado, tenemos el Google.Protof que nos indica los tipos que se van a usar para los tipos del proto. El Grpc.Tool es el encargado de generar la implementación del servicio base de nuestro proto sin tirar una línea de código. Y por último el paquete Grpc.AspNetcore tiene el middleware que le inyectaremos para arrancar nuestro proyecto.\nImplementaremos el Servicio # Una vez ya tenemos listo nuestro proyecto para empezar a añadir la funcionalidad. En primer lugar, implementaremos el servicio. Para ello nos crearemos una carpeta \u0026ldquo;Services\u0026rdquo;, nos creamos un fichero AvengerService.cs con el siguiente contenido.\nusing Grpc.Core; using GrpcAvenger; namespace gRPCAvengers.Services { public class AvengerService : Avenger.AvengerBase { private readonly ILogger\u0026lt;AvengerService\u0026gt; _logger; private readonly IList\u0026lt;AvengerCharacter\u0026gt; _avengersCollection; public AvengerService(ILogger\u0026lt;AvengerService\u0026gt; logger) { this._logger=logger; _avengersCollection = new List\u0026lt;AvengerCharacter\u0026gt;() { new AvengerCharacter { Id =\u0026#34;1\u0026#34;, Name=\u0026#34;Hulk\u0026#34;, Photo=\u0026#34;\u0026#34;}, new AvengerCharacter { Id =\u0026#34;2\u0026#34;, Name=\u0026#34;Capitan Americana\u0026#34;, Photo=\u0026#34;\u0026#34;}, new AvengerCharacter { Id =\u0026#34;3\u0026#34;, Name=\u0026#34;Iron Man\u0026#34;, Photo=\u0026#34;\u0026#34;} }; } public override Task\u0026lt;GetAllReply\u0026gt; GetAll(Google.Protobuf.WellKnownTypes.Empty request,ServerCallContext context) { _logger.LogInformation(\u0026#34;Init GetAll\u0026#34;); var response = new GetAllReply(); response.Name.AddRange(_avengersCollection); return Task.FromResult(response); } public override Task\u0026lt;GetReply\u0026gt; Get (IdAvenger request, ServerCallContext context) { _logger.LogInformation(\u0026#34;Init GetAll\u0026#34;); var response = new GetReply { Message= _avengersCollection.Where(x =\u0026gt; x.Id.Equals(request.Name)).FirstOrDefault() }; return Task.FromResult(response); } } } Configurando el arranque # En el fichero Program.cs añadiremos la linea 4 y 8 del siguiente arranque.\nusing gRPCAvengers.Services; var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc(); var app = builder.Build(); app.MapGet(\u0026#34;/\u0026#34;, () =\u0026gt; \u0026#34;Hello Avengers Dev!!\u0026#34;); app.MapGrpcService\u0026lt;AvengerService\u0026gt;(); app.Run(); Creando el Cliente # Para la creación del cliente el proceso es muy similar al del Server. En este caso vamos a crear una aplicación de consola, aunque se puede crear cualquier tipo de proyecto.\ndotnet new console Añadiremos los siguientes paquetes Nugets, los dos primeros son exactamente igual que en el servidor y la única difencia es el tercer paquete es que será el encargado de crear el cliente gRPC con los métodos que hemos importado anteriormente.\ndotnet add package Google.Protobuf dotnet add package Grpc.Tools dotnet add package Grpc.Net.Client Una vez ya tenemos listo vamos a crear el código, no tiene mucho misterio lo que vamos a hacer es por un lado crearnos el channel donde tenemos alojado el Servidor gRPC, en este caso lo único que tenemos que establecer es la url donde esta nuestro servicio. Con ese channel podemos crear un cliente de nuestro cliente, que de esto se encarga el paquete Nuget Grpc.Net.Client. En nuestro caso hemos invocado al método que nos devuelve todos los Avengers. Si os dais cuenta nosotros no hemos implementado un método asíncrono ni síncrono, el propio Grpc.Tools es el que se encarga de crearlo. El ejemplo quedaría de la siguiente forma:\nusing Grpc.Net.Client; using GrpcAvenger; using var channel = GrpcChannel.ForAddress(\u0026#34;miip\u0026#34;); var client = new Avenger.AvengerClient(channel); var empty = new Google.Protobuf.WellKnownTypes.Empty(); var reply = await client.GetAllAsync(empty); foreach (var item in reply.Name) { Console.WriteLine($\u0026#34;{item.Id}-{item.Name}-{item.Photo}\u0026#34;); } Console.WriteLine(\u0026#34;Press any key to exit...\u0026#34;); Console.ReadKey(); Resumiendo # Nos queda pendiente muchas cosas, el testing de esta aplicación, el ponerla en una aplicación más seria, que diferencias tenemos respecto a una API Rest tradicional, casos de usos, todo esto lo abordaremos en siguientes posts. Podéis ver el ejemplo en mi repositorio de Github\nHappy codding\n","date":"4 enero 2023","externalUrl":null,"permalink":"/posts/grpc-dotnet-i/","section":"Posts","summary":"gRPC es un mecanismo moderno de invocación de métodos remotos (RPC) entre procesos/sistemas/servicios, desarrollado por Google. Utiliza como transporte HTTP/2 y Protocol Buffers como lenguaje de descripción de interfaz\n","title":"gRPC \u0026 Dotnet por dónde empezar?","type":"posts"},{"content":"","date":"18 octubre 2022","externalUrl":null,"permalink":"/posts/globalazure-2022/","section":"Posts","summary":"","title":"Global Azure - Evolución de tu desarrolla usando Azure","type":"posts"},{"content":"","date":"18 octubre 2022","externalUrl":null,"permalink":"/posts/kdc-2022/","section":"Posts","summary":"","title":"Kubernetes Day Spain - Como adoptar Kubernetes en una gran corporacion","type":"posts"},{"content":"","date":"18 octubre 2022","externalUrl":null,"permalink":"/posts/netcoreconf-madrid-2022/","section":"Posts","summary":"","title":"NetCoreConf Madrid 2022","type":"posts"},{"content":"","date":"18 octubre 2022","externalUrl":null,"permalink":"/posts/spslive-2022/","section":"Posts","summary":"","title":"SharePoint Saturday live - Docker for M365","type":"posts"},{"content":"","date":"17 septiembre 2022","externalUrl":null,"permalink":"/posts/netcoreconf-barcelona-2022/","section":"Posts","summary":"","title":"NetCoreConf Barcelona 2022","type":"posts"},{"content":"Todos los años suelo hacer un balance de lo que me ha ido bien y de lo que me ha ido mal, los últimos años solamente me bastaba con un Tweet en el que indicaba que no me sentía bien o que no estaba satisfecho. Este año creo que me daba para algo más que un tweet en el que poder transmitir como ha sido este año 2021 siempre desde mi punto de vista y que me planteo para el 2022.\n¿ Como ha sido este año 2021 profesionalmente? # Para mi este año 2021 profesionalmente creo que ha sido un punto de inflexión bastante grande. Miro todo lo que he realizado este año y creo que ahora mismo he mejorado muchas skills que tenía olvidadas o que ni siquiera sabía que las tenía. Supongo que mucho de vosotr@s ya lo sabéis, pero hace un año tome una decisión que era abandonar mi casa ENCAMINA, cuando llegaron estas fechas e hice este mismo balance y con todas las circunstancias que nos rodeaban tome la decisión de que necesitaba un cambio de aires. El factor pandemia ha provocado en mi un cambio de pensar tanto personalmente como profesionalmente, y es que no me vale el ser feliz dentro de dos años sino quiero ser feliz ahora, el vivir él ahora es lo que nos podemos llevar porque el futuro es algo tan volátil que y cambiante que pueden cambiar muchísimas cosas. Está bien tener un objetivo a largo plazo… pero como largo plazo no algo que necesites en tu día a día, si tu día a día ya necesitas cosas actúa porque si no vas a dejar de ser feliz y en la vida si algo he aprendido es que hay que ser feliz y vida solo tenemos una y si no somos felices en esta vida, decirme cuando vamos a ser felices?\nEl cambio fue además de la empresa un poco en el puesto de trabajo, un cambio de rol. Ahora mismo soy el Squad Technical Lead de uno de los productos más críticos de la compañía como es un Producto de Autenticación para muchos millones de usuarios. Tras ese título esotérico cuales son las tareas que he tenido que hacer:\nLiderar técnicamente el producto, abordar cuales son las soluciones que debemos de afrontar para cumplir con los requerimientos que tiene el producto. Aquí hay una frase que nuestra misión es pensar cómo vamos a solucionar los problemas de mañana, en lugar de estar abajo en las trincheras poniendo los mimbres para que el producto no se caiga. En términos prácticos, pues el estar viendo que aspectos podemos mejorar y como lo podemos mejorar. Un claro ejemplo cuando salió .NET 5 en producción en menos de un mes teníamos el producto listo en producción adaptado. ¿Porque lo hicimos? Porque lo que se gana en perfomance con el esfuerzo que implica migrarlo era casi obligatorio mandarlo. Pero el liderar no implica ser un dictador y hacer lo que uno considere, sino que somos un equipo y el equipo decide todo lo que le pasa al producto. Cuáles son sus preocupaciones, que aspectos podemos mejorar, que aspectos necesitan de un refactor\u0026hellip; Cuando hablamos de la parte técnica, no solo hablo de la parte relacionada con Azure/Office 365/Backend sino que soy responsable de todas las partes del producto. Un producto que tiene parte Backend, FrontEnd, QA, SalesForce, Infraestructura, Monitorizaciones, mantenimiento, support, SLA\u0026rsquo;s. En este punto creo que he sido donde he mejorado. Primero en el sistema de Quality Assurance, una labor que nunca había sido participe de ella, más que mirar algún bug \u0026hellip; pero ahora en primer lugar tanto QA como el Product Owner como mi rol deben de estar de acuerdo de que es lo que quiere hacer el producto y en base a eso ir descendiendo en los diversos eslabones de desarrollo y puesta en producción y que todo el equipo aporte y de nuevas soluciones o se cuestione estas modificaciones/requerimientos que llegan. Luego una vez la tarea ya la tenemos en el Backlog como se somete a los procesos de pruebas tanto manual como automáticas. En este punto tengo la suerte de tener en el equipo a una verdadera crack Yurian (aka La jefa) que exige lo mejor de cada uno del equipo. Luego el punto de las pruebas de automation era algo relativamente nuevo para mí, en casi ningún proyecto anteriormente los habíamos tenido y mucho menos a un especialista. En este punto siempre surge la necesidad de que sea lo que debemos de testear y en qué momento lo debemos de hacer. El integrar todo esto en el proceso de integración está siendo una labor muy importante y que nos está ayudando mucho. Luego el tema de monitorización es algo que por regla general es algo que se tenía abandonado. ¿Por qué? Al final en un proyecto en consultoría, entregas el proyecto y luego ya no estas en esas fases de estabilización y mantenimiento. Ahora como tú eres el mismo responsable, sabes que si hay un fallo lo tienes que solucionar tú mismo. Por eso es importante toda esta labor, en que consiste esta labor. Es algo que hace todo el equipo y es una rutina más. Mirar los indicadores, las alertas y ver si se puede mejorar. En caso de que se pueda mejorar y solucionarlo. También el tema del despliegue, al final tenemos un producto que tiene mucho tráfico y hemos tenido un reto muy importante es como hacer un despliegue con 0 Down time a la hora del día que queremos. Es algo que siempre he prestado mucha atención, pero nunca había trabajado en un proyecto con tanto trabajo y notoriedad.\nTener un aspecto de Management en mi Squad somos 8 personas, a alguno de ellos ya los conocía. Es más, quería volver a trabajar con Juan Carlos pero la gran mayoría del Squad son personas con los que nunca había trabajado, me imponía un poco y me daba/da un poco de miedo. En ciertos momentos muchas veces estas acostumbrado a tu rutina que el cambiar de empresa pues es un reset de todo\u0026hellip; igual el Adri de Encamina había demostrado mucho pero aquí no he demostrado nada y tengo que demostrarlo. Eso siempre da un poco de miedo, salir de la zona de confort, el tener que volver a demostrar cosas, el cuestionarte todo. En ese aspecto es uno de los que más satisfecho estoy, somos un equipo que remamos todos en la misma dirección, nuestro objetivo es que el producto sea cada vez mejor y para ello todos somos participes del mismo. Para mi ell@s han sido mi mejor regalo, es muy fácil currar con ell@s. Los que me conocéis sabéis que soy un Pitufo Gruñón porque me suelo enfadar mucho, aunque se me pase rápido \u0026hellip; este año creo que me he enfadado 1 o 2 veces con ellos, no se creen que me enfado, pero yo se lo recuerdo por si se me vuelve a cruzar el cable.\nAlineamiento con Bussiness, dentro de nuestra estructura el Product Owner y el Squad Technical Lead tienen que estar coordinados y alineados en el devenir del producto. En mi trayectoria previa siembre he discutido con muchos jefes de proyecto, intentando ahorrar tiempos de desarrollo, limitando aspectos de mejora. En cambio, ahora aquí tengo la suerte de trabajar con Sergio Zurita Su labor de ser un miembro más, de mirar por lo que el equipo dice. De no bajar el ruido al equipo, de ser consciente de las mejoras que hay que hacer para ser mejores. Para mí es alguien esencial dentro del Squad y seguro que sin él igual este año no hubiera sido igual. Pero tengo la suerte de tenerlo en mi equipo.\nYa he contado un poco algunas de las labores, pero lo que me ha hecho volver a ser feliz, es tener un producto en el que se ha innovado, se ha pensado una arquitectura para soportar 100 millones de usuario, un sistema espectacular que no voy/puedo dar más detalles. Tener un equipo comprometido y todos mirando hacia el mismo lado eso es algo que no está pagado.\nLuego y no menos importante el aprender de muchos de los cracks con los que estoy rodeado, he tenido la suerte de tener en el equipo a Fernando Escolar mira que sabía que era bueno \u0026hellip; pero es que no solo lo parece, sino que más bueno de lo que parece, sus locuras nos guían y nos hacen mejorar, estoy contando los días para que vuelvas de tu baja y lo sabes.\nMención aparte con mi hermano Robert, yo quería volver a trabajar con él y un año después volvería a seguir trabajando con él mil veces más. Su forma de ser, su carácter, sus ganas de mejorar, de superarse y de estar al 200 % es algo que no está pagado. Tengo que decir que este año me ha reñido mucho más que en mis últimos 5 años, pero eso me ha obligado a ser mucho mejor. El que sea mi hermano no implica que no me deje de reñir o exigirme, es algo que ninguno de los dos hubiéramos permitido y no le estaré lo suficientemente agradecido de poder darme esta oportunidad.\nY uno de mis objetivos que tenía en este 2020 era mejorar mi nivel de inglés, he estado todo el año dando clases de inglés y en la última semana discutí con unos compañeros alemanes así que creo que puedo decir que también lo puedo dar por bueno, ¿no? Aunque aún tengo que mejorar, cuando me veáis dando una charla en ingles podré decir que he cumplido este objetivo.\nA nivel de comunidad, aunque haya estado igual de activo, volví a ser nombrado MVP por Microsoft. Pero tengo que reconocer que el formato virtual me está agotando, yo necesito los eventos físicos como el comer. Poder volver a vivir, disfrutar y compartir tecnología. Ir a cualquier ciudad y volver a dar abrazos y tomarnos un patxaran en cualquier lugar del mundo es algo que no sabíamos lo importante que era hasta que no lo hemos tenido. Espero en este nuevo año que podamos volver a este tipo de eventos con más normalidad y volver a veros a muchos de vosotros.\nY si has llegado hasta este punto, mil gracias. Espero que estés bien de salud. Que el pasado es eso pasado y que este nuevo año se cumplan lo que os propongáis. Yo para este 2022 también tengo muchos nuevos objetivos que ya repasaremos a final del año que viene si se han cumplido o no 😊\nHappy year :)\n","date":"30 diciembre 2021","externalUrl":null,"permalink":"/posts/bye2021-hello2022/","section":"Posts","summary":"Todos los años suelo hacer un balance de lo que me ha ido bien y de lo que me ha ido mal, los últimos años solamente me bastaba con un Tweet en el que indicaba que no me sentía bien o que no estaba satisfecho. Este año creo que me daba para algo más que un tweet en el que poder transmitir como ha sido este año 2021 siempre desde mi punto de vista y que me planteo para el 2022.\n","title":"Bye Bye 2021, Hello 2022","type":"posts"},{"content":"","date":"30 diciembre 2021","externalUrl":null,"permalink":"/categories/personal/","section":"Categories","summary":"","title":"Personal","type":"categories"},{"content":"","date":"30 diciembre 2021","externalUrl":null,"permalink":"/tags/personal/","section":"Tags","summary":"","title":"Personal","type":"tags"},{"content":"","date":"21 octubre 2021","externalUrl":null,"permalink":"/tags/cloud/","section":"Tags","summary":"","title":"Cloud","type":"tags"},{"content":"Uno de los puntos más críticos en cualquier plataforma es como ir subiendo las versiones de los productos, la finalidad es clara el estar en la última versión siempre nos proporciona una serie de ventajas. Sin embargo, también nos lleva una serie de retos, cuando lo abordamos en desarrollo puede ser algo más sencillo porque podemos tener los dos proyectos de forma simultánea\u0026hellip;. pero con la infraestructura que puede ocurrir? Sin infraestructura nuestra aplicación no funciona, por lo tanto, toda atención que se le puede prestar siempre es poco. En este artículo vamos a ver la forma en la que lo hemos planteado y que problemas/soluciones hemos aplicado para ir subiendo de la versión de Terraform 0.12 a la versión 1.0.4.\nIntroducción # Antes de empezar la faena vamos a intentar poner un poco de contexto a este artículo con la finalidad de intentar que el lector entienda el contexto y le pueda servir de utilidad. Empezaremos por la base, hasta que lleguemos al caso de uso que estamos hablando. Espero no enrollarme mucho e ir directo al grano (aunque muchas veces no se puede).\n¿Qué es Terraform? # Es un programa para gestionar IaC (Infraestructura como código). Define una estructura y sintaxis generales para el código y lo ejecuta para alcanzar el estado deseado.\nPor decirlo de alguna forma declaramos como queremos tener la infraestructura, Terraform se encarga de chequear la infraestructura y si existe alguna modificación aplica estos cambios, en caso de que se lo indiquemos. Por este motivo hay que tener muy presente cual es el ciclo de vida que tenemos en Terraform. Terraform Init =\u0026gt; Inicializa el directorio de trabajo que consta de todos los archivos de configuración Plan =\u0026gt; El plan se utiliza para crear un plan de ejecución para alcanzar el estado deseado de la infraestructura. Los cambios en los archivos de configuración se realizarán para lograr el estado realizado. Apply =\u0026gt; Realiza los cambios en la infraestructura tal y como se define en el plan y la infraestructura llega al estado deseado. Destroy =\u0026gt; Se utiliza para eliminar los recursos de infraestructura antiguos. ¿Como funciona Terraform? # ¿Qué es cada cosa? En los ficheros con extensión \u0026ldquo;.TF\u0026rdquo; son la fuente de entrada en la que se define lo que se debe de crear o aprovisionare. Los ficheros con extensión \u0026ldquo;.tfstate\u0026rdquo; es donde está el estado en el que se encuentra el estado actual de la infraestructura. Lo que hace Terraform es comprobar el \u0026ldquo;estado\u0026rdquo; en el que estaba junto con los ficheros de configuración, junto con el acceso al proveedor donde queremos ejecutar estos cambios. Si al realizar esta comprobación existe algo que ha cambiado en la infraestructura en el plan se nos notificará de este cambio. Cuando pasamos un Plan hay que observar todos los cambios que se aplican porque cualquier cambio que se produzca va a provocar cambios en la infraestructura. ¿Esto implica que una vez pase un Terraform no puedo tocar nada desde el portal de Azure? La respuesta es que NO, se puede modificar cualquier valor desde el Portal, eso sí, si ese cambio posteriormente no lo bajamos al terraform la siguiente vez que pasemos el Plan volverá al último estado anterior.\n¿Qué nos beneficia Terraform respecto a utilizar el lenguaje nativo de cada Cloud? # Mucha gente pone como principal ventaja que Terraform nos proporciona una capa que es agnóstica al Cloud en el que lo estamos ejecutando. Si mañana llega y migramos toda nuestra infraestructura de Azure a AWS, Google o el que sea no tendríamos que hacer ningún cambio\u0026hellip; sin embargo, ¿cuántas veces hacemos esto? ¿Cuántos clientes cambian de proveedor de Cloud? Al final, esto es un ejemplo de cuando nos planteamos usar un ORM porque si en un futuro cambiamos de Base de Datos que no tendremos que realizar ninguna modificación y será trasparente\u0026hellip; y cuantas veces cambiamos de BD en un proyecto. Pues con Terraform y esta ventaja para mí esto no es una ventaja ni algo por lo que tengamos que decidir usar Terraform en lugar de ARM por ejemplo. Entonces, ¿que nos ofrece Terraform que nos ofrece Azure Resource Manager? Al final desde mi punto de vista para mí la opción de utilizar Terraform es que disponemos de un lenguaje declarativo en el que podemos fabricar nuestros módulos, para establecer nuestras propiedades y que estas las podamos aplicar a todos nuestros desarrollos y vincular estas propiedades al ciclo de vida. De forma que la creación de estos módulos nos simplifique mucho la vida a la hora de crear nuevos entornos y también tener una serie de tags/tallas preestablecidas para tener unificado todo. De esta forma tenemos un control de la infraestructura de la misma forma que hacemos con el código fuente de nuestro desarrollo, para mi esta es la mejor opción.\nEste último punto es muy importante a la hora de utilizar Terraform pero el objetivo principal de este articulo NO nos olvidemos que es como migrar de una versión a otro de Terraform sin morir en el intento.\nComo migramos de versión de Terraform # Actualmente Terraform está por la versión 1.0.9, previamente paso por la 0.1.x hasta la 0.15.x. Si vamos a la documentación oficial tenemos una serie de guías de como subir de versión. Podéis consultarlo en este enlace. Ahora bien, porque debemos/necesitamos migrar de una versión a otra, que nos aporta, que beneficios tiene. Si vemos la documentación nos dice el cómo y lo que afecta \u0026hellip; pero otro tema es como nosotros lo podemos hacer. La primera cuestión es que para la creación de infraestructura en la implementación de una serie de módulos que comenzaron su implementación en la versión más reciente que tenía el producto en esa versión. Estos módulos se van evolucionando según las necesidades que tiene ese producto y según lo que se utilice, esto puede provocar que tengamos unos módulos en una versión de Terraform superior a otros\u0026hellip; lo que puede provocar un caos y muchas referencias circulares como os podéis imaginar. Ahora bien, cuál es la tarea vamos a migrar todos los módulos que tenemos a la última versión estable que tiene Terraform, al final si nuestros ficheros de infraestructura no se han modificado, el estado de Terraform no debe de aplicar casi ningún cambio y mucho menos recrear infraestructura. ¿Pero cuál es la realidad? Seguro que os lo podéis imaginar, que cualquier parecido con la realidad puede ser pura coincidencia\u0026hellip;. A la hora de migrar un módulo de Terraform de una versión a otra que nos podemos encontrar:\nque se añaden nuevos campos que antes no se informaban y ahora sí. que campos ahora ya no son necesarios que se tenga que recrear infraestructura por estos cambios Todas estas casuísticas hay que ver cómo abordarlas. Las dos primeras en la gran mayoría de los casos no implican ninguna parada del servicio y el aplicarlas en cualquier momento es un trámite (o debería podemos preguntar a la banda local un par de errores nuevos). ¿Sin embargo, en el caso de que nos pida recrear la infraestructura\u0026hellip; que debemos de hacer? En primer lugar, tirarnos de los pelos yo no he cambiado nada quiero un recurso y no estoy haciendo ningún cambio que requeria hacerlo. Una vez pasado este estado de no entender la cosa nos toca parar a pensar. ¿Porque nos pide recrear la infraestructura y por este motivo debemos de entender cómo funciona Terraform que nos está diciendo el plan? Nos está diciendo que se van a agregar nuevas funcionalidades (algo que hace solo por el hecho de subir versión). Ahora bien, ¿por qué? Voy a poner un caso que nos ha ocurrido a nosotros, en nuestro caso nosotros a la hora de aprovisionar el Redis. Que estaba pasando en el momento que nosotros creamos el módulo no existía un módulo que generase el failover y siguiendo las indicaciones que había teníamos un código similar al siguiente:\nresource \u0026#34;null_resource\u0026#34; \u0026#34;create_failover\u0026#34; { count = local.create_replica \u0026amp;\u0026amp; local.redis_name != \u0026#34;\u0026#34; ? 1 : 0 depends_on = [ azurerm_redis_cache.redis_caches ] provisioner \u0026#34;local-exec\u0026#34; { command = \u0026#34;az login --service-principal -u ${var.client_id} -p ${var.client_secret} --tenant ${var.tenant_id} \u0026amp;\u0026amp; az account set --subscription=${var.subscription_id} \u0026amp;\u0026amp; az redis server-link create --name ${local.redis_name} --replication-role Secondary --resource-group ${local.redis_resource_group} --server-to-link ${local.redis_link_id}\u0026#34; } } resource \u0026#34;null_resource\u0026#34; \u0026#34;delete_failover\u0026#34; { count = local.create_replica ? 1 : 0 depends_on = [ azurerm_redis_cache.redis_caches ] provisioner \u0026#34;local-exec\u0026#34; { when = destroy command = \u0026#34;az login --service-principal -u ${var.client_id} -p ${var.client_secret} --tenant ${var.tenant_id} \u0026amp;\u0026amp; az account set --subscription=${var.subscription_id} \u0026amp; az redis server-link delete --linked-server-name ${local.redis_link_name} --name ${local.redis_name} --resource-group ${local.redis_resource_group}\u0026#34; on_failure = continue } } Para poder migrar este código de la versión 0.12 y pasarlo a la versión 0.13 tendríamos que poner este código\nresource null_resource\u0026#34; \u0026#34;create_failover\u0026#34; { count = local.create_replica \u0026amp;\u0026amp; local.redis_name != \u0026#34;\u0026#34; ? 1 : 0 depends_on = [ azurerm_redis_cache.redis_caches ] provisioner \u0026#34;local-exec\u0026#34; { command = \u0026lt;\u0026lt;-EOT az login --service-principal --username ${data.azurerm_client_config.current.client_id} --password=${var.client_secret} --tenant ${data.azurerm_client_config.current.tenant_id} az redis server-link create --name ${local.redis_name} --replication-role Secondary --resource-group ${local.redis_resource_group} --server-to-link ${local.redis_link_id} --subscription ${data.azurerm_client_config.current.subscription_id} EOT } } resource \u0026#34;null_resource\u0026#34; \u0026#34;delete_failover\u0026#34; { count = local.create_replica ? 1 : 0 depends_on = [ azurerm_redis_cache.redis_caches ] triggers = { client_id = data.azurerm_client_config.current.client_id client_secret = var.client_secret subscription_id = data.azurerm_client_config.current.subscription_id tenant_id = data.azurerm_client_config.current.tenant_id redis_name = local.redis_name redis_link_name = local.redis_link_name redis_resource_group = local.redis_resource_group } provisioner \u0026#34;local-exec\u0026#34; { when = destroy command = \u0026lt;\u0026lt;-EOT az login --service-principal --username ${self.triggers.client_id} --password=${self.triggers.client_secret} --tenant ${self.triggers.tenant_id} az redis server-link delete --linked-server-name ${self.triggers.redis_link_name} --name ${self.triggers.redis_name} --resource-group ${self.triggers.redis_resource_group} --subscription ${self.triggers.subscription_id} EOT on_failure = continue } } ¿Qué ocurre en este caso cuando pasamos el plan? Pues que para Terraform el Failover lo que iba a realizar es recrear el recurso, eso que implica que destruye el Redis y lo vuelve a crear. Pero ¿porque Terraform funciona así? Al final lo que le indica es que se el failover se han añadido unas propiedades nuevas internas y tiene que volver a recrear el recurso. ¿Sabemos que implica esto? Que durante el tiempo que tarde esto NO tendremos el Redis. Imaginar que es la cache que tenemos en nuestro sistema en Producción con 60 millones de usuario. Lo que nos implica que bien tenemos que hacer una parada del aplicativo durante el tiempo que dure este proceso. Aproximadamente este proceso dura unos 25 minutos si todo va bien.\n¿Tenemos alguna alternativa más? Imaginar hacer una parada de un sistema mínimo 30 minutos sin obtener mucha ganancia. Que alternativas veis posibles para no poder hacer una parada del servicio:\nCrearnos un Redis intermedio que lo podamos \u0026ldquo;pivotar\u0026rdquo; mientras nuestro Redis está en reconstrucción. El principal hándicap (más allá de un coste mientras el proceso esta ejecutado) es como pasamos los datos de la cache de un momento a otro. Esta opción en nuestro caso podría ser una alternativa porque disponemos de procesos en background que se encargan de rellenar los datos de la cache, pero podría darse el caso que alguna información que estuviera alojada en la cache se perdiera. Ver si con Terraform tenemos alguna opción de poder indicarla a nuestro sistema que ya tiene un failover y que coja ese estado como punto de partida. Como seguro que habéis deducido la opción 2 es posible, y sería la mejor desde el punto de vista que se vea: económico, tiempo y afectación del servicio. ¿Qué tenemos que hacer? En este caso en Terraform salio la parte de soportar el failover en Redis de forma nativa. De esta forma en lugar de usar el comando de az-cli usamos la opción que nos da Terraform de forma nativa. Para soportar esto hay que realizar una modificación en el módulo, antes de subir de versión de Terraform, dejándolo de la siguiente forma:\nlocals { create_replica = var.naming.is_high_availability \u0026amp;\u0026amp; var.is_geo_replica redis_name = local.create_replica ? try(azurerm_redis_cache.redis_caches[\u0026#34;primary\u0026#34;].name, \u0026#34;\u0026#34;) : \u0026#34;\u0026#34; redis_resource_group = local.create_replica ? try(azurerm_redis_cache.redis_caches[\u0026#34;primary\u0026#34;].resource_group_name, \u0026#34;\u0026#34;) : \u0026#34;\u0026#34; redis_link_id = local.create_replica ? try(azurerm_redis_cache.redis_caches[\u0026#34;secondary\u0026#34;].id, \u0026#34;\u0026#34;) : \u0026#34;\u0026#34; redis_link_name = local.create_replica ? try(azurerm_redis_cache.redis_caches[\u0026#34;secondary\u0026#34;].name, \u0026#34;\u0026#34;) : \u0026#34;\u0026#34; redis_link_location = local.create_replica ? try(azurerm_redis_cache.redis_caches[\u0026#34;secondary\u0026#34;].location, \u0026#34;\u0026#34;) : \u0026#34;\u0026#34; } resource \u0026#34;azurerm_redis_linked_server\u0026#34; \u0026#34;failover\u0026#34; { count = local.create_replica \u0026amp;\u0026amp; local.redis_name != \u0026#34;\u0026#34; ? 1 : 0 target_redis_cache_name = local.redis_name resource_group_name = local.redis_resource_group linked_redis_cache_id = local.redis_link_id linked_redis_cache_location = local.redis_link_location server_role = \u0026#34;Secondary\u0026#34; } Ahora bien, si hacemos este cambio y lanzamos un plan vemos que aún nos indica que tiene dos acciones: eliminar el failover y volver a crearlo. ¿Como podemos hacer que no lo destruya? Es importante tener claro cuál es el ciclo de vida de Terraform (lo que hemos visto en el inicio del articulo). Tenemos que indicarle a Terraform cuál es el estado de la infraestructura que tenemos arriba. ¿Como se puede hacer? Para ello importamos el estado con el siguiente comando\nterraform import -var-file terraform.dev.tfvars module.redis.azurerm_redis_linked_server.failover[0] /subscriptions/[guid]/resourceGroups/[name_grupo_recursos]/providers/Microsoft.Cache/Redis/[name_redis]/linkedServers/[name_redis_linked] Una vez ya tenemos el estado importado, volvemos a lanzar el plan y en este momento el único cambio que tiene que hacer es eliminar las settings que tenía previamente. La eliminación de estas variables no implica la destrucción de ninguna infraestructura por lo que podemos aplicar los cambios sin ninguna afectación. Cuando ya tenemos este cambio realizado en nuestra versión actual de Terraform, podemos subir de versión de Terraform sin ningún problema.\nResumiendo brevemente el proceso a realizar # Al final el paso de una versión de Terraform a otra depende de muchos factores, que módulos estas utilizando, como los estas utilizando. Por lo tanto, a la hora de plantearse una migración de versión hay que tener muy claro cuál es la estrategia y que pros y contras nos podemos encontrar. Seguro que en los primeros proyectos en lo que lo abordemos podemos pensar que en que follón nos hemos metido\u0026hellip; pero lo luego una vez vas migrando los proyecto al final te sientes cómodo y estas confiado de haber realizado la mejor opción.\nHappy codding :)\n","date":"21 octubre 2021","externalUrl":null,"permalink":"/posts/migrar-terraform/","section":"Posts","summary":"Uno de los puntos más críticos en cualquier plataforma es como ir subiendo las versiones de los productos, la finalidad es clara el estar en la última versión siempre nos proporciona una serie de ventajas. Sin embargo, también nos lleva una serie de retos, cuando lo abordamos en desarrollo puede ser algo más sencillo porque podemos tener los dos proyectos de forma simultánea…. pero con la infraestructura que puede ocurrir? Sin infraestructura nuestra aplicación no funciona, por lo tanto, toda atención que se le puede prestar siempre es poco. En este artículo vamos a ver la forma en la que lo hemos planteado y que problemas/soluciones hemos aplicado para ir subiendo de la versión de Terraform 0.12 a la versión 1.0.4.\n","title":"Como migrar de versión de Terraform y no morir en el intento","type":"posts"},{"content":"","date":"21 octubre 2021","externalUrl":null,"permalink":"/categories/devops/","section":"Categories","summary":"","title":"Devops","type":"categories"},{"content":"","date":"21 octubre 2021","externalUrl":null,"permalink":"/tags/migration/","section":"Tags","summary":"","title":"Migration","type":"tags"},{"content":"","date":"21 octubre 2021","externalUrl":null,"permalink":"/tags/terraform/","section":"Tags","summary":"","title":"Terraform","type":"tags"},{"content":"Seguimos con la segunda edición de la NetCoreConf, seguimos con el formato virtual, un poco cansado esta segunda edición, muchas sesiones en directo, el cuerpo nos pide eventos pero echamos de menos mucho de menos el contacto físico. Al hilo de la sesión que dimos un poco con el cambio que he tenido este año, un poco de monitorizar un producto, saber las tripas del mismo es impostantisimo saber como hacerlo y un poco esta charla el sentido de la misma era dar ese punto de vista de un developer que por regla general hace el desarrollo y se va.\n","date":"10 septiembre 2021","externalUrl":null,"permalink":"/posts/netcoreconf-virtual-2021-monitoring-developers/","section":"Posts","summary":"Seguimos con la segunda edición de la NetCoreConf, seguimos con el formato virtual, un poco cansado esta segunda edición, muchas sesiones en directo, el cuerpo nos pide eventos pero echamos de menos mucho de menos el contacto físico. Al hilo de la sesión que dimos un poco con el cambio que he tenido este año, un poco de monitorizar un producto, saber las tripas del mismo es impostantisimo saber como hacerlo y un poco esta charla el sentido de la misma era dar ese punto de vista de un developer que por regla general hace el desarrollo y se va.\n","title":"NetCoreConf Virtual 2021 Monitoring for Developers","type":"posts"},{"content":"","date":"10 septiembre 2021","externalUrl":null,"permalink":"/categories/workshop/","section":"Categories","summary":"","title":"Workshop","type":"categories"},{"content":"El pasado 27/28 de febrero organizamos la primera edición del 2021 de la NetCoreConf. Desde que no hemos podido hacer eventos físicos hemos estado dandole vueltas al coco con tal de conseguir este objetivo. En este artículo voy a explicar como lo hemos intentado en la NetCoreConf, herramientas que hemos utilizado, como las hemos utilizado configurado y los motivos de porque hemos utilizado así.\nUna vez terminamos la conferencia hablando con la gente me dijeron ya tienes titulo para la siguiente charla \u0026ldquo;Como ser youtuber y no morir en el intento\u0026rdquo; bueno como aún nos queda un tiempo para tener un evento físico pues me he decidido a escribirlo por si a algún otro evento online le puede valer o servir como idea.\nObjetivos # El objetivo que nos habíamos planteado en esta conferencia es que fuera lo más parecido a un evento físico.\nPara ello hicimos una serie de cosas:\nPlanteamos a los speakers que quisieran hacer la sesión en directo. Nuestra idea es que solamente fuera un track a lo sumo o como mucho una tarde. Sin embargo, nuestra sorpresa es que el 50% de las sesiones las querian hacer en directo\u0026hellip; Para ello y tras buscar muchas soluciones decidimos hacer tres tracks de 12 horas cada uno en directo. Para ello tendríamos que buscar que plataforma/herramientas de como poder hacerlo. La otra novedad que hicimos fue la inclusión de un BAR virtual, para ello usamos Discord y gracias a la idea de Fernando Escolar y con la ayuda de Juan Quijano y El Bruno Una vez finalizo la conferencia, contando como nos había ido, todos llegamos a la misma conclusión ser Youtuber(streamer mejor dicho) es mucho más dificil que lo que pensabamos\u0026hellip; llego el domingo y todos estabamos literalmente cansados y contentos a partes iguales\nEmisión en Directo # Introducción # Con la nueva modalidad de hacer eventos online hemos ido buscando la forma/herramientas de intentar acercar la conferencia a las personas. Desde que hemos hace un año empezo la pandemia hemos estado buscando herramientas para poder tener una interacción publico/speaker para poder tener ese calorcito\u0026hellip; antes de que todo esto ocurriese a lo largo de la comunidad hemos realizado varios intentos de formaciones onlines como herramientas como LiveMeetings, Hangouts y en las últimas fechas Microsoft Teams había sido la más predominante. Sin embargo, con Teams teníamos algunos inconvenientes:\nEl sistema de eventos en directo no permite una interacción directa entre el presentador y los asistentes, solamente un sistema de preguntas y respuestas que nos parece algo pobre. El utilizar una meeting de Teams Tampoco es una alternativa por muchos motivos: todos los asistentes necesitan tener Teams, pueden hablar, compartir, tenemos una limitación en cuanto a participantes.. y luego al final se necesita una plataforma donde todos los participantes se pueda visualizar el evento una vez finalizado. Esta claro que Youtube es una plataforma \u0026ldquo;gratuita\u0026rdquo; donde todo el mundo puede visualizar los videos. Ahora bien, como lo habiamos echo hasta la fecha en algunos Meetup\u0026rsquo;s de CatZure? Habíamos usado StreamYard. Para quien no conozca esta herramienta, es un servicio al cual se acceder via web en el que podemos personalizar el fondo, poner el logo e ir añadiendo participantes de una forma muy sencilla. De la misma forma se puede conectar para hacer un streeming directamente con Youtube y/o Twitch y tiene herramientas para poder contestar a las personas en dichos chats. Sin embargo, de cara a la organización de este evento el uso de esta herramienta no se podía llevar a cabo. ¿Por qué?\nQueremos hacer un evento de más de 12 horas y con StreamYard existe una limitación de 4 un máximo de 4 horas Necesitabamos emitir en más de una plataforma contenido totalmente diferente. Cual fue la solución # El Frontend # La solución que habiamos planteado era buscar alguna herramienta en la que pudieramos emitir directamente desde una webcamp y viendo la que utilizaba el mayor número de personas encontramos OBS. Para quien no conozca OBS es un proyecto de software libre y de código abierto para la grabación de video y transmisión en vivio que se instala tanto en Windows, Mac o Linux. Ahora bien, para empezar a usar OBS tuvimos dos retos por un lado tener un skin personalizado para poder emitir y que tuviera nuestra personalización y por otro lado ver como podiamos hacer que los speakers estuvieran conectados a nuestra producción en tiempo real.\nPara el tema de la personalización, existen muchos templates free que poder usar \u0026hellip; pero nosotros somos algo distinto y que digan lo que digan y por este motivo decidimos con la ayuda de un diseñador nos aportará un poco de luz al asunto y nos diera un template con el que nos identificaramos. La cosa quedo algo como esto. ¿Quedo chula no? Pero una vez, tenemos el template se puede pensar que ya esta todo echo en cuanto a la emisión sin embargo aun quedaba mucha faena por hacer. Por un lado, ajustar las escenas que se pueden emitir, una cosa es el template base y otra como podemos adaptarlo a nuestra realidad. Por poner un ejemplo teniamos una escena en la que saliamos los 4 organizadores y al lado habia un chat. Habia que configurar ese chat en OBS tanto para Twitch como para Youtube, teniamos que adaptar la emisión para dependiendo donde estuviera transmitiendo el speaker se visualizará en cualquier parte de la pantalla y no molestará ni impidiera que no se visualice el código/presentación que estuviera mostrando. Luego el domingo habiamos planteado una serie de entrevistas con los speakers y tuvimos que adaptar alguna de esas escenas subsituyendo el chat por la cámara del invitado\u0026hellip; e incluso tuvimos a dos entrevistados de forma simultanea y para lo cual nos toco al vuelo y en pleno directo ir modificando el template para poder adaptarla y que salieran los dos speakers a la vez.\nEl Backend # Ya teniamos claro que es lo que iba a ser el frontal, pero ahora nos faltaba como poder añadir a los speakers dentro del escenario, para ello usamos Microsoft Teams. Ahora la pregunta es como podemos incluir una interfaz tan fija y caracteristica como es la de Teams dentro del Template que habiamos diseñado. Pues por un lado OBS tiene un plugin para obtener la emisión de un canal NDI en el que esta la maquina y por otro lado una reunión de Teams se puede emitir en este formato NDI.\nPara muchos NDI nos sonaba a chino, pero NDI es un estandard y que se utiliza mucho en el mundo de las retransmisitions. \u0026ldquo;NDI, cuyo nombre proviene de Network Device Interface, es una tecnología de interfaz de dispositivo de red bastante novedosa en el campo audiovisual. Viene a sustituir al SDI, ya que con su innovadora tecnología supone el intercambio de información de video por IP a través de redes Ethernet y representa el flujo de trabajo basado en IP más productivo del sector. Por tanto, NDI es un estándar libre de derechos que permite superar la habitual limitación en el mundo audiovisual de entornos de producción en directo, estando interconectados y con posibilidades infinitas, donde cada dispositivo se conecta a los demás y cada fuente supone ser también un destino. Es capaz de unificar protocolos de vídeo a través de IP para crear al momento un entorno de instalación para diferentes plataformas.\u0026rdquo; fuente\nAhora bien, ¿pero Teams Tiene esa característica de emisión en NDI?? La respuesta es que sí, pero para ello en primer lugar un Administrador de Teams debería de permitirlo por politicias en primer lugar. Para ello desde la administración de Teams Reuniones-Directivas de Reunión hay que activar lo de permitir la transmisión por secuencias NDI Una vez desde la Administración de Teams, ya está habilitada necesitamos hacer uso de la App de Teams. Esta característica no esta habilitada para Teams en modo Web ni naturalmente en la App para los dispositivos moviles. Para ello en primer lugar debemos de arrancar Teams en modo Administrador (dependiendo de los niveles de permisos que tengamos en nuestra máquina es posible que no tengamos habilitado esta opción), una vez arrancado vamos a la Configuración\u0026ndash;\u0026gt;Permisos y habilitamos el NDI, como vemos en la siguiente imagen. En el caso de que no tengamos esta opción es posible que en nuestro Tenant no tengamos el NDI activado o bien no hemos arrancado como administrador.\nY una vez realizado todo esto aún nos queda una cosa más\u0026hellip; es dentro de la propia reunión en la que vamos a emitir tenemos que habilitar \u0026ldquo;BroadCast Over NDI\u0026rdquo; Una vez tenemos habilitado el NDI y tenemos cualquier reunión de Teams habilitada lo que tenemos que hacer es configurar el OBS para que uno de los origines de datos sean las Camaras Web que hay en la reunión de Teams y también el escritorio con esto hemos conseguido que tengamos un Skin chulo y por detras toda la potencia de Teams :) Ahora bien, una vez ya sabeis que era el backend. Dentro de Teams nos creamos tres canales \u0026ldquo;Twitch\u0026rdquo;, \u0026ldquo;Youtube Room1”,\u0026ldquo;Youtube Room2\u0026rdquo; en la que dentro de cada canal creamos una convocatoria a cada uno de los speakers que iban a dar una sesión de tal forma que cuando era la hora de su sesión se conectaban para ellos era transparente y teniamos su comunicación de una forma sencilla y simple y que todo el mundo estaba acostumbrado. Sin embargo, a toro pasado creo que nos equivocamos al crear un canal porque con el canal si el usuario no tiene licencia no podemos hablar directamente con ellos y cualquier percance que ocurra pues es \u0026ldquo;vox-populi\u0026rdquo; jajja\nUna última cosa que tuvimos encuentra fue que ibamos a emitir más de 12 horas y si ibamos a imitir en nuestras casas era posible que durante algún tiempo se produciera un problema en la conexión de internet o cualquier cosa por ese motivo nos creamos unas maquinas virtual en Azure con la siguiente caracteristica: El Bar # Aquí fue otra de las cosas que fue una de las grandes novedades\u0026hellip; Fernando Escolar tuvo la brillante idea de darle una vuelta de tuerca. Para los que nos conoceis de los eventos físicos muchas veces estamos en el bar más cercano comentando frikadas técnicas tomandonos una cerveza para esos momentos en los que ya estas cansado entre sesión y sesión y es una de las cosas chulas que se echa de menos en los eventos \u0026ldquo;virtuales\u0026rdquo;. Pues para simular este \u0026ldquo;Bar\u0026rdquo; nos creamos un Discord, de forma que habilitamos un \u0026ldquo;Bar\u0026rdquo; donde todos podian habilitar su webcam y entablar conversación de una forma más entretenida y poder desvirtual a mucha gente y volver a hablar con la gente. A esta idea le sumamos con la ayuda de Juan Quijano y El Bruno para que aprovechando su Podcast No tiene nombre que puedieran entrevistar a la gente y tener un recuerdo único y que más alla de los videos que quedarán permanentes en Youtube tengamos un sitio donde hablar y plasmar este momento.\nEl discord además de tener este Bar tenia una parte entre bambalinas en la clasica Sala de Speakers donde muchos de los speakers hablarón los días previos\u0026hellip; y bueno una parte de comunicación entre la organización y los speakers lejos del frio mail.\nQuien quiera entrar en el Discord se puede unir en la siguiente [dirección] (https://discord.gg/QfEeAcPPpr)\nEspero que os haya gustado este artículo, por lo menos casí tanto como a nosotros hacer esta edición especial de la Netcoreconf. La organización Robert, Txema, Manu y Adri\n","date":"13 marzo 2021","externalUrl":null,"permalink":"/posts/como-ser-youtuber/","section":"Posts","summary":"El pasado 27/28 de febrero organizamos la primera edición del 2021 de la NetCoreConf. Desde que no hemos podido hacer eventos físicos hemos estado dandole vueltas al coco con tal de conseguir este objetivo. En este artículo voy a explicar como lo hemos intentado en la NetCoreConf, herramientas que hemos utilizado, como las hemos utilizado configurado y los motivos de porque hemos utilizado así.\n","title":"Como ser YouTuber y no morir en el intento","type":"posts"},{"content":"","date":"13 marzo 2021","externalUrl":null,"permalink":"/tags/comunidad/","section":"Tags","summary":"","title":"Comunidad","type":"tags"},{"content":"","date":"13 marzo 2021","externalUrl":null,"permalink":"/tags/teams/","section":"Tags","summary":"","title":"Teams","type":"tags"},{"content":"","date":"13 marzo 2021","externalUrl":null,"permalink":"/tags/twitch/","section":"Tags","summary":"","title":"Twitch","type":"tags"},{"content":"","date":"13 marzo 2021","externalUrl":null,"permalink":"/tags/youtube/","section":"Tags","summary":"","title":"Youtube","type":"tags"},{"content":"Primera charla del año, otra vez la NetCoreConf nuestro hijo que hemos visto crecer y que es un orgullo ver la gente que participa, que quiere participar, que nos ayuda y bueno esta edición ha sido un poco más especial ya que acercamos un poco más a los eventos físicos que tanto echamos de menos\u0026hellip; pero bueno eso será para otro post que se esta conociendo mientras si quereis ver nuestra sesión y el resto de sesiones que emitimos están en el canal de Youtube de CatZure (y de paso sino estas subscrit@ pues nos harías un gran favor)\n","date":"7 marzo 2021","externalUrl":null,"permalink":"/posts/netcoreconf-virtual-2021-equipo-a/","section":"Posts","summary":"Primera charla del año, otra vez la NetCoreConf nuestro hijo que hemos visto crecer y que es un orgullo ver la gente que participa, que quiere participar, que nos ayuda y bueno esta edición ha sido un poco más especial ya que acercamos un poco más a los eventos físicos que tanto echamos de menos… pero bueno eso será para otro post que se esta conociendo mientras si quereis ver nuestra sesión y el resto de sesiones que emitimos están en el canal de Youtube de CatZure (y de paso sino estas subscrit@ pues nos harías un gran favor)\n","title":"NetCoreConf Virtual 2021 Como al equipo A casi siempre le sabel bien los planes","type":"posts"},{"content":"Cuando nos enfrentamos con procesos que son susceptibles de emitir excepciones existen varios patrones o estrategias que podemos seguir para gestionar el comportamiento ante el error. En este articulo vamos a ver como usando la librería Polly podemos llevar a cabo un patrón de Fallback para poder dar respuesta a nuestras necesidades.\n¿Qué es Polly? # Polly es una librería Open Source para .NET que permite implementar ciertos patrones de resiliencia y gestión/recuperación de excepciones en aplicaciones .NET. Su uso simplifica el proceso permitiéndonos implementar los patrones como Retry, Timeout o Circuit-breaker de forma robusta y de forma muy simple a través de su sintaxis Fluent. Está pensada principalmente para procesos Web. Así veréis que muchos ejemplos disponibles se centran en temas relativos a peticiones Http. Sin embargo, Polly puede ser útil en una gran cantidad de situaciones y proyectos, aunque no estén en absoluto relacionados con la comunicación.\nPatrón Fallback # Es un patrón de resiliencia para poder lidiar con fallos (latencia y caídas del sistema, entre otros). El patrón fallback proporciona un mecanismo a través del cual ofrecer una alternativa ante un servicio que está fallando.\nEjemplo # Por ubicar un ejemplo que podamos entender todos, tenemos una API de Avengers en el que dentro de esta API consulta un servicio que nos ha proporcionado Disney para devolver todos los Avengers que siguen en nómina. El flujo de nuestra aplicación seria el siguiente:\nConsultamos el servicio que nos proporciona Disney. a) si nos devuelve que la comunicación ha ido perfecta devolvemos los resultados que nos proporciona el servicio. b) si falla en lugar de atacar el servicio Disney, vamos a consultar unos datos que tenemos en una base de datos. Este ejemplo implementa el patrón Fallback, pero existen muchos patrones que se pueden aplicar dependiendo de nuestras necesidades como por ejemplo, Timeout, Retry, Circuit-Breaker .. En algunos escenarios más complejos se pueden concatenar varios de estos patrones. El flujo quedaría de la siguiente forma: Manos a la obra # Api Disney # En primer lugar nos vamos a crear la API de Disney, en esta API es un proyecto .NET Core de tipo API, sin mucho más misterio.\ndotnet new webapi Crearemos un fichero .json con los datos que va a devolver la API. A nivel de código tendremos un controlador de la siguiente forma.\nusing Disney.API.Model; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.Collections.Generic; namespace Disney.API.API { [Route(\u0026#34;api/disney/[controller]\u0026#34;)] [ApiController] public class AvengersController : ControllerBase { [HttpGet] public IEnumerable\u0026lt;Avenger\u0026gt; Get() { var file = System.IO.File.ReadAllText(\u0026#34;./Data/data.json\u0026#34;); var result = JsonConvert.DeserializeObject\u0026lt;IEnumerable\u0026lt;Avenger\u0026gt;\u0026gt;(file); return result; } } } Api Avengers # Al igual que en la anterior API, nos creamos un proyecto .NET Core de tipo API\ndotnet new webapi En este proyecto añadiremos el paquete de Polly\ndotnet add package Polly En primer lugar, vamos a implementar un método \u0026ldquo;GetAvengersDisney\u0026rdquo; que es el que va a llamar a la API de Disney, quedaría de la siguiente forma\nprivate IEnumerable\u0026lt;Avenger\u0026gt; GetAvengersDisney() { var request = new HttpRequestMessage(HttpMethod.Get, \u0026#34;api/disney/avengers\u0026#34;); var client = _clientFactory.CreateClient(\u0026#34;disney\u0026#34;); var response = client.SendAsync(request).Result; if (response.IsSuccessStatusCode) { using var responseStream = response.Content.ReadAsStreamAsync().Result; var avenger = System.Text.Json.JsonSerializer.DeserializeAsync\u0026lt;IEnumerable\u0026lt;Avenger\u0026gt;\u0026gt;(responseStream).Result; return avenger; } else { throw new Exception(); } } A continuación, nos crearemos el método que se va a ejecutar cuando la API de Disney no esté disponible. Por eso nos creamos el siguiente método \u0026ldquo;GetAvengersLocal\u0026rdquo;\nprivate IEnumerable\u0026lt;Avenger\u0026gt; GetAvengersLocal() { var file = System.IO.File.ReadAllText(\u0026#34;./Data/data.json\u0026#34;); var result = JsonConvert.DeserializeObject\u0026lt;IEnumerable\u0026lt;Avenger\u0026gt;\u0026gt;(file); return result; } Con todo esto ya tenemos listo para poder ver cómo funciona la \u0026ldquo;magia\u0026rdquo; de Polly. En este caso hemos simplificado el uso de la librería para que pueda ser mucho más entendible en este ejemplo. Vamos a poner el siguiente código en el controlador\n[HttpGet] public IEnumerable\u0026lt;Avenger\u0026gt; Get() { IEnumerable\u0026lt;Avenger\u0026gt; result =null ; var fallBack = Policy .Handle\u0026lt;Exception\u0026gt;() .Fallback(() =\u0026gt; { result = this.GetAvengersLocal(); }); fallBack.Execute(() =\u0026gt; { result = this.GetAvengersDisney(); }); return result; } Que hace dicho código, nos definimos una Policy en la que vamos a indicar que en el caso de que se produzca una Excepción se lance el método que definamos en Fallback. Una vez definida la Policy lanzamos la \u0026ldquo;Action\u0026rdquo; que queremos hacer. ¿Como funciona? Primero se ejecuta la Action y en el caso de que se produzca una Excepción se lanzará la función que hemos puesto en el método Fallback.\n¿Todo esto no lo podemos poner si nosotros controlamos la Excepción? La respuesta es que sí, si nos ceñimos a un caso básico, pero conforme nuestra aplicación tiene cada vez más complejidad y queremos que pueda adaptarse a los patrones cloud, hay que tener en cuenta cada vez más cosas y seguro que un simple try/catch se nos queda muy corto a las necesidades.\nResumen # Polly es una librería muy sencilla que nos permite poder implementar muchos patrones de resiliencia, de esta forma podamos tener nuestras aplicaciones más robustas y que se recuperen ante posibles imprevistos. En muchas de las aplicaciones que hacemos el tiempo en el que se recuperen ante errores es algo muy importante. Por citar un ejemplo que ha sido muy sonado últimamente las webs para poder atender a las reservas de las consolas de última generación sin que el sistema se caiga. Suponiendo que está alojado en Azure, y que por cada reserva se lanza un \u0026ldquo;evento\u0026rdquo; en background. Este evento podemos decir que es un mensaje en una cola de un service Bus. En la cola del Service Bus tenemos asociada una Function donde se encarga de realizar la reserva. Que ocurre cuando la cola del service Bus este llena. ¿Dejamos de atender “reservas”? Está claro que NO \u0026hellip; pues con patrones como Fallback podemos implementar que cuando el Service Bus no funcione (por el motivo que sea), dejemos ese evento en otro \u0026ldquo;artefacto\u0026rdquo; por ejemplo una cola de un Storage, o simplemente se almacena en una base de datos. Podéis descargaros el ejemplo y ver el funcionamiento en el siguiente repositorio\nHappy codding :)\n","date":"24 enero 2021","externalUrl":null,"permalink":"/posts/polly-fallback/","section":"Posts","summary":"Cuando nos enfrentamos con procesos que son susceptibles de emitir excepciones existen varios patrones o estrategias que podemos seguir para gestionar el comportamiento ante el error. En este articulo vamos a ver como usando la librería Polly podemos llevar a cabo un patrón de Fallback para poder dar respuesta a nuestras necesidades.\n","title":"Como implementar el patrón FallBack con Polly","type":"posts"},{"content":"","date":"24 enero 2021","externalUrl":null,"permalink":"/tags/polly/","section":"Tags","summary":"","title":"Polly","type":"tags"},{"content":"","date":"24 enero 2021","externalUrl":null,"permalink":"/tags/resilencia/","section":"Tags","summary":"","title":"Resilencia","type":"tags"},{"content":"Y tras el workshop llego la hora de la conferencia, en esta ocasión la sorpresa es que intentamos que todo el mundo tuviera un recuerdo de la conferencia e intentamos trasladar la esencia de un evento fisico a uno virtual.. su galleta, sus goodies, el networking (en los chats de las sesiones). Fue mucho trabajo previo, organizar muchisimas cosas pero el terminar y saber que las cosas salieron mucho mejor que en la primera sesión. El crecimiento del Canal de Youtube fue una barbaridad más de 800 subscriptores, muchos gente visualizando las charlas, muchas horas emitidas y nosotros al pie del cañon disfrutando del evento.\nEn cuanto a la sesión, en esta ocasión di una charla conjunta con mi hermano Robert en la que nos tiramos una hora hablando y contando nuestras experiencias de como poder montar arquitecuras de alta disponibilidad en Azure. Cuando la terminamos de grabar pensamos uff nuestra primera sesión conjunto sin enseñar nada de código igual ha quedado un poco densa\u0026hellip; pero por el feedback que recibimos creemos que gusto Aqui la tienes por si quereis echarle un ojo.\n","date":"4 octubre 2020","externalUrl":null,"permalink":"/posts/netcoreconf-virtual2-arquitecturas-alta-disponibilidad2020/","section":"Posts","summary":"Y tras el workshop llego la hora de la conferencia, en esta ocasión la sorpresa es que intentamos que todo el mundo tuviera un recuerdo de la conferencia e intentamos trasladar la esencia de un evento fisico a uno virtual.. su galleta, sus goodies, el networking (en los chats de las sesiones). Fue mucho trabajo previo, organizar muchisimas cosas pero el terminar y saber que las cosas salieron mucho mejor que en la primera sesión. El crecimiento del Canal de Youtube fue una barbaridad más de 800 subscriptores, muchos gente visualizando las charlas, muchas horas emitidas y nosotros al pie del cañon disfrutando del evento.\n","title":"NetCoreConf Virtual 2 Arquitecturas de alta disponibilidad","type":"posts"},{"content":"Volvio la NetCoreConf y con ello volvimos a intentar realizar algún cambio en los eventos virtuales e intentamos añadir un día de Workshop donde tuvimos la suerte de que nos ayudaran grandes referentes de la Comunidad: Rocio Romero, Ana Maria Bisbe, Mar Llambí, Carlos Mendible, Carlos Landeras, Javier Menendez Pallo, Luis Fraile, Sergio Navarro y tambien los miembros de la organización Txema Balseiro, Manu Sanchez, Robert Bermejo y un servidor. En el canal de Youtube estan todos los videos de workshops aqui os dejo el Workshop que dimos sobre Azure Cosmos DB Robert Bermejo y un servidor.\n","date":"3 octubre 2020","externalUrl":null,"permalink":"/posts/netcoreconf-virtual2-workshops2020/","section":"Posts","summary":"Volvio la NetCoreConf y con ello volvimos a intentar realizar algún cambio en los eventos virtuales e intentamos añadir un día de Workshop donde tuvimos la suerte de que nos ayudaran grandes referentes de la Comunidad: Rocio Romero, Ana Maria Bisbe, Mar Llambí, Carlos Mendible, Carlos Landeras, Javier Menendez Pallo, Luis Fraile, Sergio Navarro y tambien los miembros de la organización Txema Balseiro, Manu Sanchez, Robert Bermejo y un servidor. En el canal de Youtube estan todos los videos de workshops aqui os dejo el Workshop que dimos sobre Azure Cosmos DB Robert Bermejo y un servidor.\n","title":"NetCoreConf Virtual 2 Workshop First steps in Cosmos DB","type":"posts"},{"content":"","date":"29 agosto 2020","externalUrl":null,"permalink":"/categories/office-365/","section":"Categories","summary":"","title":"Office 365","type":"categories"},{"content":"","date":"29 agosto 2020","externalUrl":null,"permalink":"/tags/office-365/","section":"Tags","summary":"","title":"Office 365","type":"tags"},{"content":"","date":"29 agosto 2020","externalUrl":null,"permalink":"/tags/sharepoint/","section":"Tags","summary":"","title":"Sharepoint","type":"tags"},{"content":"A finales de Junio, se publicó en una versión estable y oficial de lo que ha sido uno de los grandes requerimientos de los desarrolladores de Office en los últimos años: CSOM para .NET Standard o lo que es lo mismo la librería Cliente para poder acceder a SharePoint. En este artículo vamos a explicar los motivos por los que se hacia esta demanda y veremos un ejemplo sencillo de como empezar a usarlo.\nHistoria # En estos momentos hablar de la importancia que tiene que una librería este en una determinada tecnología u otra mirándolo desde la perspectiva de cualquier tecnólogo puede parecer que carezca de sentido sino se tiene el contexto, y.. ¿cuál es el contexto? La forma en la que desarrolla en los productos Microsoft ha evolucionado mucho en los últimos años. Microsoft dispone de muchos productos que se utilizan en muchísimos clientes como son SharePoint, Dynamics CRM, \u0026hellip; Estos productos hace no muchos años se instalan o instalaban en la infraestructura de estos clientes y el desarrollo se realizaba instalando \u0026ldquo;artefactos\u0026rdquo; en dicho servidor. Ahora bien no es necesario indicar que los tiempos cambian y estos productos ahora son servicios que consumimos en el Cloud y naturalmente no tenemos acceso al servidor para instalar nada. Esto implico un gran cambio en la forma de desarrollar sobre SharePoint, pasamos de un desarrollo en lenguajes de servidor como C# a especialistas en el FrontEnd. La cuestión no es solo un cambio de tecnología cambiar C# por Typescript (con el frameworks que más rabia nos dé), sino que muchas de las acciones que se hacían en los desarrollos ahora no se pueden hacer. Acciones como poder impersonar usuarios al estar en el Front no se puede hacer (y aunque se pudiera no sería una opción que se debería de plantear), procesos \u0026ldquo;programados\u0026rdquo;/\u0026ldquo;bajo demanda\u0026rdquo; para integración con otros sistemas. Para poder hacer esta funcionalidad había que utilizar CSOM( Client SharePoint Object Model) o bien directamente llamadas a la API Rest de SharePoint. La problemática en este punto es que la API Rest de SharePoint no tiene todos los endpoint ni toda la funcionalidad que hay en CSOM y a parte CSOM es mucho más fácil de utilizar. En este punto CSOM fue relativamente importante tanto para añadirlo en API Rest Custom o bien en el uso de Azure Functions. En las primeras versiones cuando Azure Functions en su versión 1.0 era .NET Framework y .NET Core estaba en sus primeras versiones (que había muchas dudas sobre su evolución y el camino que iba a seguir) el desarrollador de Office podía extender su desarrollo de una forma natural. Conforme aumentaron las versiones de dichos productos y su implantación fue mayor, el poder hacer uso de una Azure Function en el contexto de SharePoint era algo relativamente complejo. Muchos en este punto pensareis que necesidades pueda tener de hacer uso de SharePoint en una Azure Functions, poner el caso de que tengo unos documentos en SharePoint y quiero mandarlos en un almacenamiento más ligero para tenerlo en un historio como pudiera ser un Blob Storage. Para hacer este desarrollo teníamos dos opciones:\nHacer uso de Azure Functions en versión 1.0 (con sus contras: obsoleto, tuning del toolings de herramientas) Hacer uso de Azure Functions en versión 2.0 o superior, pero tener en cuenta que la comunicación con todo lo relacionado con SharePoint se tendria que poner una \u0026ldquo;pieza\u0026rdquo;(API Rest) implementada en .NET Framework para poder hacer esta forma. Como podéis ver cualquiera de estas opciones no es la mejor, y que independientemente de la que escojamos elegimos la menos mala. Ahora bien, con la llegada de CSOM en .NET Standard todos estos impedimentos ya nos los tenemos, ahora tenemos otras cosas. Las primeras preguntas que tenemos son ¿CSOM en .NET Framework y Standard tienen las mismas opciones? ¿Son iguales?\n¿Qué cosas podemos hacer con CSOM en .NET Core o más bien cuáles son sus limitaciones? # Lo primero que indica la documentación es que esta versión de CSOM solo es compatible con la versión Online de SharePoint. En las versiones OnPremise no está actualizada ni soportada.\nEl principal cambio del uso de esta librería es que no soporta ningún método que tenga la autenticación SharePointOnlineCredentials, el uso de la autenticación basada en usuario/contraseña es un método común para los programadores que usan CSOM para .NET Framework. En CSOM para .NET Standard esto ya no es posible, es el desarrollador que usa CSOM para .NET Standard para obtener un token de acceso de OAuth y usarlo para realizar llamadas a SharePoint Online. El método recomendado para obtener tokens de acceso para SharePoint Online es configurar una aplicación de Azure AD. Para CSOM para .NET Standard lo único que importa es que se obtenga un token de acceso válido, ya que puede usar el flujo de credenciales de la contraseña del propietario del recurso, mediante el inicio de sesión de dispositivo, mediante la autenticación basada en certificados,\nEn el tema de métodos de CSOM no se han reescrito:\nSaveBinaryDirect / OpenBinaryDirectAPI (basada en WebDAV)=\u0026gt; El motivo es claro para poder llegar al fichero es necesario tener una autenticación que hoy en día no está permitida. El uso de estos métodos se hacen dependiendo del tamaño de ficheros que queremos realizar (esto daría para un artículo solamente). Microsoft.SharePoint.Client.Utilities.HttpUtility =\u0026gt; Todos estos métodos que se utilizaban están escritos en el propio Framework con lo cual han hecho caso al no tener dos librerías que hacen lo mismo EventReceiver =\u0026gt; Aunque parezca mentira aún estaban este tipo de Artefactos soportados en el Frameworks, aunque todo el mundo ya usaba como desencadenadores otros artefactos de Azure como las mencionadas Azure Functions, Logic Apps .. etc Veamos un ejemplo # Crear la aplicación en el Azure Active Directory # Ir a Azure AD portal a través de https://aad.portal.azure.com Haga clic en Azure Active Directory y en los registros de aplicaciones en el panel de navegación izquierdo Haga clic en nuevo registro Escriba un nombre para la aplicación y haga clic en registrar. Vaya a permisos de la API para conceder permisos a la aplicación, haga clic en Agregar permiso, elija SharePoint, permisos delegados y seleccione por ejemplo AllSites. administrar Haga clic en conceder consentimiento de administrador para aceptar los permisos solicitados de la aplicación Haga clic en autenticación en el panel de navegación izquierdo Cambiar tipo de cliente predeterminado-tratar aplicación como cliente público de no a sí Haga clic en información general y copie el identificador de la aplicación en el portapapeles (lo necesitaremos más adelante). Crear la Aplicación de Consola # Crearemos una aplicación de consola. Bien usando la línea de comando o bien directamente desde Visual Studio dotnet new console Añadiremos los siguientes paquetes de nuget . Microsoft.SharePointOnline.CSOM . System.IdentityModel.Token.Jwt Nos creamos una clase autenticacion manager using Microsoft.SharePoint.Client; using System; using System.Collections.Concurrent; using System.Net.Http; using System.Security; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Web; namespace Example_CSOM_NET_Standar { public class AuthenticationManager : IDisposable { private static readonly HttpClient httpClient = new HttpClient(); private const string tokenEndpoint = \u0026#34;https://login.microsoftonline.com/common/oauth2/token\u0026#34;; private const string defaultAADAppId = \u0026#34;clientid\u0026#34;; // Token cache handling private static readonly SemaphoreSlim semaphoreSlimTokens = new SemaphoreSlim(1); private AutoResetEvent tokenResetEvent = null; private readonly ConcurrentDictionary\u0026lt;string, string\u0026gt; tokenCache = new ConcurrentDictionary\u0026lt;string, string\u0026gt;(); private bool disposedValue; internal class TokenWaitInfo { public RegisteredWaitHandle Handle = null; } public ClientContext GetContext(Uri web, string userPrincipalName, SecureString userPassword) { var context = new ClientContext(web); context.ExecutingWebRequest += (sender, e) =\u0026gt; { string accessToken = EnsureAccessTokenAsync(new Uri($\u0026#34;{web.Scheme}://{web.DnsSafeHost}\u0026#34;), userPrincipalName, new System.Net.NetworkCredential(string.Empty, userPassword).Password).GetAwaiter().GetResult(); e.WebRequestExecutor.RequestHeaders[\u0026#34;Authorization\u0026#34;] = \u0026#34;Bearer \u0026#34; + accessToken; }; return context; } public async Task\u0026lt;string\u0026gt; EnsureAccessTokenAsync(Uri resourceUri, string userPrincipalName, string userPassword) { string accessTokenFromCache = TokenFromCache(resourceUri, tokenCache); if (accessTokenFromCache == null) { await semaphoreSlimTokens.WaitAsync().ConfigureAwait(false); try { // No async methods are allowed in a lock section string accessToken = await AcquireTokenAsync(resourceUri, userPrincipalName, userPassword).ConfigureAwait(false); Console.WriteLine($\u0026#34;Successfully requested new access token resource {resourceUri.DnsSafeHost} for user {userPrincipalName}\u0026#34;); AddTokenToCache(resourceUri, tokenCache, accessToken); // Register a thread to invalidate the access token once\u0026#39;s it\u0026#39;s expired tokenResetEvent = new AutoResetEvent(false); TokenWaitInfo wi = new TokenWaitInfo(); wi.Handle = ThreadPool.RegisterWaitForSingleObject( tokenResetEvent, async (state, timedOut) =\u0026gt; { if (!timedOut) { TokenWaitInfo wi = (TokenWaitInfo)state; if (wi.Handle != null) { wi.Handle.Unregister(null); } } else { try { // Take a lock to ensure no other threads are updating the SharePoint Access token at this time await semaphoreSlimTokens.WaitAsync().ConfigureAwait(false); RemoveTokenFromCache(resourceUri, tokenCache); Console.WriteLine($\u0026#34;Cached token for resource {resourceUri.DnsSafeHost} and user {userPrincipalName} expired\u0026#34;); } catch (Exception ex) { Console.WriteLine($\u0026#34;Something went wrong during cache token invalidation: {ex.Message}\u0026#34;); RemoveTokenFromCache(resourceUri, tokenCache); } finally { semaphoreSlimTokens.Release(); } } }, wi, (uint)CalculateThreadSleep(accessToken).TotalMilliseconds, true ); return accessToken; } finally { semaphoreSlimTokens.Release(); } } else { Console.WriteLine($\u0026#34;Returning token from cache for resource {resourceUri.DnsSafeHost} and user {userPrincipalName}\u0026#34;); return accessTokenFromCache; } } private async Task\u0026lt;string\u0026gt; AcquireTokenAsync(Uri resourceUri, string username, string password) { string resource = $\u0026#34;{resourceUri.Scheme}://{resourceUri.DnsSafeHost}\u0026#34;; var clientId = defaultAADAppId; var body = $\u0026#34;resource={resource}\u0026amp;client_id={clientId}\u0026amp;grant_type=password\u0026amp;username={HttpUtility.UrlEncode(username)}\u0026amp;password={HttpUtility.UrlEncode(password)}\u0026#34;; using (var stringContent = new StringContent(body, Encoding.UTF8, \u0026#34;application/x-www-form-urlencoded\u0026#34;)) { var result = await httpClient.PostAsync(tokenEndpoint, stringContent).ContinueWith((response) =\u0026gt; { return response.Result.Content.ReadAsStringAsync().Result; }).ConfigureAwait(false); var tokenResult = JsonSerializer.Deserialize\u0026lt;JsonElement\u0026gt;(result); var token = tokenResult.GetProperty(\u0026#34;access_token\u0026#34;).GetString(); return token; } } private static string TokenFromCache(Uri web, ConcurrentDictionary\u0026lt;string, string\u0026gt; tokenCache) { if (tokenCache.TryGetValue(web.DnsSafeHost, out string accessToken)) { return accessToken; } return null; } private static void AddTokenToCache(Uri web, ConcurrentDictionary\u0026lt;string, string\u0026gt; tokenCache, string newAccessToken) { if (tokenCache.TryGetValue(web.DnsSafeHost, out string currentAccessToken)) { tokenCache.TryUpdate(web.DnsSafeHost, newAccessToken, currentAccessToken); } else { tokenCache.TryAdd(web.DnsSafeHost, newAccessToken); } } private static void RemoveTokenFromCache(Uri web, ConcurrentDictionary\u0026lt;string, string\u0026gt; tokenCache) { tokenCache.TryRemove(web.DnsSafeHost, out string currentAccessToken); } private static TimeSpan CalculateThreadSleep(string accessToken) { var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(accessToken); var lease = GetAccessTokenLease(token.ValidTo); lease = TimeSpan.FromSeconds(lease.TotalSeconds - TimeSpan.FromMinutes(5).TotalSeconds \u0026gt; 0 ? lease.TotalSeconds - TimeSpan.FromMinutes(5).TotalSeconds : lease.TotalSeconds); return lease; } private static TimeSpan GetAccessTokenLease(DateTime expiresOn) { DateTime now = DateTime.UtcNow; DateTime expires = expiresOn.Kind == DateTimeKind.Utc ? expiresOn : TimeZoneInfo.ConvertTimeToUtc(expiresOn); TimeSpan lease = expires - now; return lease; } protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { if (tokenResetEvent != null) { tokenResetEvent.Set(); tokenResetEvent.Dispose(); } } disposedValue = true; } } public void Dispose() { // Do not change this code. Put cleanup code in \u0026#39;Dispose(bool disposing)\u0026#39; method Dispose(disposing: true); GC.SuppressFinalize(this); } } } Con esta clase ya podemos obtener un Token valido y hacer consultas contra SharePoint. Por ejemplo: public static async Task Main(string[] args) { Console.WriteLine(\u0026#34;Introduce el tenant\u0026#34;); var tenant = Console.ReadLine(); Console.WriteLine(\u0026#34;Introduce el usuario\u0026#34;); var user = Console.ReadLine(); Console.WriteLine(\u0026#34;Introduce el passworkd\u0026#34;); var rawPassword = Console.ReadLine(); Uri site = new Uri($\u0026#34;https://{tenant}.sharepoint.com/\u0026#34;); SecureString password = new SecureString(); foreach (char c in rawPassword) password.AppendChar(c); using (var authenticationManager = new AuthenticationManager()) using (var context = authenticationManager.GetContext(site, user, password)) { context.Load(context.Web, p =\u0026gt; p.Title); await context.ExecuteQueryAsync(); Console.WriteLine($\u0026#34;Title: {context.Web.Title}\u0026#34;); } } y el resultado seria el siguiente\nConclusiones # Que CSOM lo podamos utilizar en NET Standard es un gran paso para todos los desarrolladores de SharePoint, con la llegada de .NET 5 y los cambios que implican era algo que era cuestión de tiempo que acabara sucediendo sin embargo su adelanto es algo de gran ayuda para todos los desarrolladores de Office. A nivel de aprendizaje los métodos son exactamente iguales y por lo tanto no requiere de ningún aprendizaje extra.\nHappy codding\nCodigo Fuente en Github # https://github.com/AdrianDiaz81/Example-CSOM-NET-Standar\nReferencias # https://docs.microsoft.com/es-es/sharepoint/dev/sp-add-ins/using-csom-for-dotnet-standard\n","date":"29 agosto 2020","externalUrl":null,"permalink":"/posts/sharepoint-csom-netcore/","section":"Posts","summary":"A finales de Junio, se publicó en una versión estable y oficial de lo que ha sido uno de los grandes requerimientos de los desarrolladores de Office en los últimos años: CSOM para .NET Standard o lo que es lo mismo la librería Cliente para poder acceder a SharePoint. En este artículo vamos a explicar los motivos por los que se hacia esta demanda y veremos un ejemplo sencillo de como empezar a usarlo.\n","title":"SharePoint CSOM ya disponible en .NET Standard","type":"posts"},{"content":"El pasado jueves como viene siendo tradicional los últimos años en CatZure creamos una charla con amigos y además los son de los mejores especialistas en Azure y su mundo relacionado. Estrenamos un nuevo formato e hicimos uso de Twicht como plataforma para emitirlo en directo y poder tener la interacción de la Comunidad en vivo y en directo. Mucho estamos hablando de que echamos de menos la presencialidad el vernos, abrazornos, tomarnos una birra, como decía Carlos Mendible esas conversaciones de pasillo entre sesión y sesión. Y aunque este evento no lo sustituyo si que con la nueva plataforma tuvimos un pequeño backstage en el que estuvimos hablando y acercandonos a la nueva \u0026ldquo;normalidad\u0026rdquo; \u0026hellip; pero que me enrollo hubieron muchisimas preguntas y casi la mitad de la que nos hicieron se quedaron en el tintero yo de vosotr@s si no lo habeís visto ya no me lo perderia. Aquí os dejo el video antes de empezar las vacaciones, cargar un poco las pilas, desconectar y ser feliz.\n","date":"2 agosto 2020","externalUrl":null,"permalink":"/posts/azure-summer-talks-community/","section":"Posts","summary":"El pasado jueves como viene siendo tradicional los últimos años en CatZure creamos una charla con amigos y además los son de los mejores especialistas en Azure y su mundo relacionado. Estrenamos un nuevo formato e hicimos uso de Twicht como plataforma para emitirlo en directo y poder tener la interacción de la Comunidad en vivo y en directo. Mucho estamos hablando de que echamos de menos la presencialidad el vernos, abrazornos, tomarnos una birra, como decía Carlos Mendible esas conversaciones de pasillo entre sesión y sesión. Y aunque este evento no lo sustituyo si que con la nueva plataforma tuvimos un pequeño backstage en el que estuvimos hablando y acercandonos a la nueva “normalidad” … pero que me enrollo hubieron muchisimas preguntas y casi la mitad de la que nos hicieron se quedaron en el tintero yo de vosotr@s si no lo habeís visto ya no me lo perderia. Aquí os dejo el video antes de empezar las vacaciones, cargar un poco las pilas, desconectar y ser feliz.\n","title":"Azure Summer Talks Community","type":"posts"},{"content":"","date":"14 julio 2020","externalUrl":null,"permalink":"/tags/odata/","section":"Tags","summary":"","title":"Odata","type":"tags"},{"content":"A la hora de hacer las consultas sobre nuestras WebApi OData es un estandar propio de Microsoft que estan adoptando la mayoria de API\u0026rsquo;s de los productos punteros para establecer su comunicación con el Front. Apps como Twitter, Facebook y naturalmente Microsoft Graph utilizan esta forma para realizar las consultas a sus objetos de négocio. Como desarrolladores muchas veces no ponemos OData por el miedo al performance que ocasiona en el Backend. ¿Como lo podemos solucionar? En este articulo vemos como cuidar las consultas para de esta forma evitar matar muchos gatitos y ahorrarnos esfuerzos en el desarrollo.\nIntroducción # Este artículo es una continuación del post que escribió mi compañero Sergio Parra. Así que si no habéis oido nada sobre OData y como empezar a utilizarlo en los desarrollos en .NET ir a leerlo y luego continuar por este punto :)\nUna vez nos hemos leido el artículo anterior, sabemos lo que es OData que es un estándar que define un conjunto de buenas practicas para la construcción y consumo de RESTFul API. De todo lo que tiene Odata, centremonos en la parte para realizar consultas. Que levante la mano en cuantos proyectos en las llamadas para hacer filtros nos creamos un objeto para indicar sobre que campos, valores filtramos\u0026hellip; No digamos que es una tarea sencilla hacer eso, ¿pero a cuantos nos ha pasado que una vez instalado surge un nuevo requerimiento nuevo, no podríamos filtrar por este u otro campo y eso principalmente que implica a nivel de desarrollo? Pues en primer lugar lo que implica es que tenemos que hacer una modificación en la API y posteriormente modificar la consulta, DTO\u0026rsquo;s, Test (si los hubiera) es decir una simple modificación nos implica dedicar un tiempo que puede ser valioso.\nOtra de las ventajas que tiene OData respecto a nuestro desarrollo \u0026ldquo;Custom\u0026rdquo; es que es un Stándar. Esto hace que cualquier developer (ya sea de Front/Back) pueda entender como funciona y poder realizar las peticiones a la API que necesitan en su desarrollo.\nAhora bien, este último punto, tambien puede ser un inconveniente dependiendo de la madurez del equipo de desarrollo. Pensar un supuesto en el que solamente se cree un unico controlador con OData y se permita realizar cualquier filtro y consulta sobre dicha entidad. Esto puede hacer que para establecer determinada logica de negocio, en lugar de implementar un método en el controlador con dicha lógica se indique que se haga esta logica implementando filtros con OData. Para mi este supuesto es un mal uso de OData, si hemos dicho que nos ahorra tiempo de implementación, pero esto no implica que en cada entidad tengamos que indicar sobre que campos queremos filtrar y sobre todo que queremos mostrar. Pensar un caso que tengamos una lista de clientes y tengamos un filtro en el que mostremos cualquier información sobre él, por ejemplo, información tan sustancial como el importe que nos adeuda\u0026hellip; No creo que la mejor opción sea que implementemos OData y con ella poder permitir cualquier opción.\nComo incorporar OData # Vamos a partir del que tenemos una API de Avengers en primer lugar vamos a configurar a instalar los paquetes Nuget necesarios para empezar a utilizar OData en nuestra API. En este caso nos hará falta el siguiente paquete\ndotnet add package Microsoft.AspNetCore.OData --version 7.4.1 Una vez esta instalado, tocaremos el StartUp para añadirle el soporte para OData, para ello en el método Configure Services le indicaremos que vamos a hacer uso de OData para ello hay que añadir la siguientes lineas:\nservices.AddOData(); services.AddODataQueryFilter(); services.AddMvc(options =\u0026gt; { options.EnableEndpointRouting = false; }); Ahora dentro del método Configure del mismo StartUp susituiremos app.UseRouting() por el UseMvc(), con la siguiente configuración:\napp.UseMvc(routeBuilder =\u0026gt;; { routeBuilder.EnableDependencyInjection(); routeBuilder.Select().Filter().OrderBy().Expand().Count().MaxTop(10); routeBuilder.MapODataServiceRoute(\u0026#34;api\u0026#34;, \u0026#34;api\u0026#34;, GetEdmModel(app.ApplicationServices)); }); Estas líneas de código lo que realizan es:\nActivar la inyección de Dependencias Ver que métodos de OData vamos a permitir: Select, Filter, OrderBy, Expand, Count, Top.. Mapear OData con nuestro modelo para ello nos creamos el método GetEdm que tiene el siguiente código: private static IEdmModel GetEdmModel(IServiceProvider serviceProvider) { ODataModelBuilder builder = new ODataConventionModelBuilder(serviceProvider); builder.EntitySet\u0026lt;Avenger\u0026gt;(\u0026#34;Avenger\u0026#34;) .EntityType .Filter() .Count() .Expand() .OrderBy() .Page() .Select(); return builder.GetEdmModel(); } Una vez tenemos el arranque configurado, vamos a tunear el controlador para ello en primer lugar es cambiar el ControllerBase por el ODataController quedando nuestro Controller de la siguiente forma:\npublic class AvengerController : ODataController { ... } Luego en lo que vamos a realizar es añadir unos decoradores al método donde vamos a usar OData, imaginar que tenemos un método que nos devuelve todos los Avengers quedando de la siguiente forma:\n[ODataRoute] [EnableQuery(PageSize = 20, AllowedQueryOptions = AllowedQueryOptions.All)] [HttpGet] public async Task\u0026lt;IActionResult\u0026gt; Get() { var result = await avengerDomain.GetAllAsync(); if (result != null) { return Ok(result); } else { return NotFound(); } } Si arrancamos nuestra API e invocamos al siguiente método localhost/api/avenger vemos que nos carga todos los items que tenemos en Avengers. Ahora si usamos la sintasis de OData para filtrar si tenemos un Avenger que se llama Hulk por ejemplo hacemos localhost/api/avenger?filter=$name eq \u0026lsquo;Hulk\u0026rsquo;\nCuál es el problema? # A nivel de la representación de los datos vemos que la devolución es la correcta y tal y como toca, pero ahora bien si analizamos las tripas y ponemos un punto de parada antes de que devolvamos los resultados veremos lo siguiente:\nComo podéis ver a pesar de que solamente hemos pedido un único registro se esta trayendo toda la información de la tabla y luego ya se encarga de hacer filtrar los datos que he pedido. Para mi esta opción no es la más optima ya que imaginar que tenemos una tabla con cantidad elevada de registros y solo queremos devolver dos, por un lado es posible que esa consulta pueda tener una penalización en cuanto a rendimiento y luego tampoco tiene sentido hacer los filtros en el servidor cuando lo puede/debe de hacer la base de datos.\nComo se puede solucionar con OData? # En primer lugar, nos hemos olvidado de un parametro que se puede pasar en el controlador donde podemos tener la consulta que se ha realiza desde la petición, es decir, tenemos un objeto donde podemos obtener la consulta que se hace y como poder utilizarla. Este objeto es ODataQueryOptions filter lo añadamos como parametro en nuestro controlador y volvemos a ejecutar la consulta y analicemos el objeto:\nSi vemos y analizamos todo el contenido que tiene ese objeto, vemos por un lado que tiene en las propiedades cada uno de los elementos que contiene el Filter, Top, Count, etc.. y tiene un método ApplyTo que hace el método ApplyTo tiene como parametro de entrada un objeto IQueryable y sobre este método se aplica el filtro que el usuario ha realizado a la Api. Muchas veces implementamos un patrón repositorio en la que tenemos unos métodos que directamente nos devuelve un método IList.\nPudiendo ser un ejemplo de Patron repositorio el siguiente código:\npublic interface IRepositoryBase\u0026lt;T, U\u0026gt; : IDisposable where T : Base\u0026lt;U\u0026gt;, new() where U : unmanaged { T Add(T entity); T GetById(U id); T GetById(U id, Func\u0026lt;IQueryable\u0026lt;T\u0026gt;, IIncludableQueryable\u0026lt;T, object\u0026gt;\u0026gt; includes); IList\u0026lt;T\u0026gt; Get(Expression\u0026lt;Func\u0026lt;T, bool\u0026gt;\u0026gt; filter); IList\u0026lt;T\u0026gt; Get(Expression\u0026lt;Func\u0026lt;T, bool\u0026gt;\u0026gt; filter, Func\u0026lt;IQueryable\u0026lt;T\u0026gt;, IQueryable\u0026lt;T\u0026gt;\u0026gt; func); bool Update(T entity); bool Delete(U id); } Muchas veces no entendemos la diferencia entres las diferentes interfaces IQueryable, IList, ICollection e IEnumerable y los motivos por los que usar unos u otros. Podríamos definir cada una de ellas de la siguiente forma\nIEnumerable =\u0026gt; Es la interfaz básica y de la que heredan el resto de interfaces. Puedes iterar a través de cada elemento del IEnumerable. No puedes editar los elementos como añadir, borrar, actualizar, etc. en su lugar sólo usas un contenedor para contener una lista de elementos. Es el tipo más básico de contenedor de listas. Todo lo que obtienes en un IEnumerable es un enumerador que ayuda a iterar sobre los elementos. Un IEnumerable no contiene ni siquiera el conteo de los elementos de la lista, en su lugar, tienes que iterar sobre los elementos para obtener el conteo de los elementos. Soporta el filtrado de elementos usando la cláusula where.\nICollection =\u0026gt; Es la más básica de las interfaces que has enumerado. Es una interfaz enumerable que soporta un Conteo y eso es todo. IList incluye todo lo que es ICollection, pero también soporta añadir y quitar elementos, recuperar elementos por índice, etc.\nIQueryable =\u0026gt; Es una interfaz enumerable que soporta LINQ. Siempre se puede crear un IQueryable a partir de un IList y usar LINQ a Objetos, pero también se encuentra el IQueryable usado para la ejecución diferida de sentencias SQL en LINQ a SQL y LINQ a Entidades.\nPartiendo de esta definición esta claro que al Repositorio Base que hemos planteado le falta unos métodos IQueryable principalmente porque en el momento en el que lancemos una .ToList() es el momento en el que se va a ejecutar la consulta. Y esta claro que no es lo mismo hacer una consulta con todos los items de la tabla que hacer una consulta con solamente la información que se necesita. Por esto añadimos al patrón repositorio anterior los siguientes métodos:\nIQueryable\u0026lt;T\u0026gt; ListAll(); IQueryable\u0026lt;T\u0026gt; ListAll(Func\u0026lt;IQueryable\u0026lt;T\u0026gt;, IQueryable\u0026lt;T\u0026gt;\u0026gt; func); Una vez tenemos añadido nuestro el método que nos devuelve el IQueryable lo que debemos de hacer es modificar nuestro método de dominio. Para ello en primer lugar le pasaremos el objeto Filter donde tenemos la consulta que se ha pedido a la API. Quedando nuestro método de la siguiente forma:\npublic IList\u0026lt;Avenger\u0026gt; GetAll(ODataQueryOptions\u0026lt;Avenger\u0026gt; filter) { var avenger = (IEnumerable\u0026lt;Avenger\u0026gt;)filter.ApplyTo(avengerRepository.ListAll()); return avenger.ToList(); } Si ahora nos pica un poco la curiosidad y analizamos las peticiones que hacemos a la base de datos con este cambio, podemos ver por ejemplo como la consulta ahora es tal y como la hemos pedido en la API, optimizando la consulta:\nAlguna cosa más? # Esto no es todo, si miráis un poco todas las posibilidades que trae OData nos da la posibilidad de traernos solo los datos que realmente necesistamos de la clase. Si establecemos una instrucción Select en la petición de OData y forzamos el tipo en el que queremos devolver se nos produce un error, debido a que no coinciden los tipos. Por ejemplo, si realizamos la siguiente instrucción:\nhttps://localhost:44339/api/avenger?$name=name\u0026amp;$filter=name eq\u0026#39;Hulk\u0026#39; Se producirá el siguiente error: Como lo podemos solucionarlo? # Lo primero que nos viene a la cabeza es no permitir la instrucción Select en OData (muerto el perro se acabo la rabia), pero lo siguiente una vez se nos pasa el cabreo por el error seria ver como poder solucionarlo. ¿Se puede solucionar? Si \u0026hellip; y podemos cambiar la devolución por un tipo Dynamic. No entraré en este artículo si conviene usarlo o no, o si es mejor o peor a nivel de ejecución. Pero partiendo de que vamos a devolver datos dependiendo de las necesidades que tengan los usuarios un Dynamic no me parece una mala opción. Como dice alguién muy cercano a mi siempre estamos decidiendo la opción menos mala \u0026hellip; pero eso para otro momento. El método quedaría de la siguiente forma:\npublic IList\u0026lt;dynamic\u0026gt; GetAll(ODataQueryOptions\u0026lt;Avenger\u0026gt; filter) { var avenger = (IEnumerable\u0026lt;dynamic\u0026gt;)filter.ApplyTo(avengerRepository.ListAll()); return avenger.ToList(); } Resumen # OData es una opción bastante fácil de pode añadir en tus desarrollos, pero no existen balas de platas y como en todo hay que saber como poder cuidar determinados aspectos del Perfomance de la base de datos y sobre todo el conocer todas las capacidades que tiene el lenguaje como las librerias que hacemos uso de ellas.\nHappy Codding :)\n","date":"14 julio 2020","externalUrl":null,"permalink":"/posts/odata-dotnet-entityframework/","section":"Posts","summary":"A la hora de hacer las consultas sobre nuestras WebApi OData es un estandar propio de Microsoft que estan adoptando la mayoria de API’s de los productos punteros para establecer su comunicación con el Front. Apps como Twitter, Facebook y naturalmente Microsoft Graph utilizan esta forma para realizar las consultas a sus objetos de négocio. Como desarrolladores muchas veces no ponemos OData por el miedo al performance que ocasiona en el Backend. ¿Como lo podemos solucionar? En este articulo vemos como cuidar las consultas para de esta forma evitar matar muchos gatitos y ahorrarnos esfuerzos en el desarrollo.\n","title":"OData y como beneficiar nuestro perfomance con Entity Framework","type":"posts"},{"content":"","date":"14 julio 2020","externalUrl":null,"permalink":"/tags/webapi/","section":"Tags","summary":"","title":"Webapi","type":"tags"},{"content":"A la hora de empezar con los test de integración tenemos varias formas de realizarlos, en este artículo os enseñare las utilidades que utilizamos para hacer testing de integración desde Visual Studio y con DotNet Core.\n¿Qué entiendo por Test de Integración? # Según unas de las definiciones que nos encontramos por Internet los test de integración son aquellas que se realizan en el ámbito del desarrollo de software una vez que se han aprobado las pruebas unitarias y lo que prueban es que todos los elementos unitarios que componen el software funcionan juntos correctamente probándolos en grupo.\nPartiendo de esta definición vamos a un escenario \u0026ldquo;común\u0026rdquo;. Este escenario es tenemos una API Rest y queremos testear todos los endpoint de los que tenemos, por ejemplo, tenemos una API que nos devuelve todos nuestros Avengers favoritos tal y como se visualiza aqui:\nAntes de empezar con los herramientas vamos a descifrar que elementos son los que tiene esta solución y así podremos ver como lo testearemos y que herramienta usaremos. Recapitulando:\nSistema de autenticación contra el Azure Active Directory Base de Datos SQL Azure usando Entity Framework ¿Que debemos de testear y como lo debemos de testear? # Para la parte del sistema de autenticación, al final lo que debemos de cubrir en un test es alguien que no esté autenticado cuando intente llamar a nuestra API le dé un código 401 Unauthorized y cuando este autenticado pues el token tenga la información necesaria para que nuestra aplicación funcione. Por lo tanto tendremos que tener algo que nos simule dicho comportamiento para ello en primer lugar .NET Core nos trae una herramienta Test Host en el que desde un proyecto de Test podemos levantar nuestra aplicación e indicarle en el StartUp la configuración que vamos a lanzar desde el proyecto de Test. Aunque como veremos después en el código es una librería muy potente y que trae muchísimas utilidades, para el tema de la seguridad deja la libertad al usuario de como obtener los tokens JWT, pues bien hay una libreria OpenSource Acheve implementada por Hugo Biarge que nos facilita la vida.\nAhora bien, para la segunda parte, el tema de la base de datos siempre es algo que hay que tener en cuenta, si va a ser un test de integración deberemos de ejecutarlo en una base de datos y no podremos mockearla. Ahora bien, estos Test en qué momento se van a ejecutar o quien los van a ejecutar. Dependiendo de estas contestaciones tenemos varias alternativas que pueden ser muy validas\nHacer uso de una base de datos InMemory. Su principal beneficio es que no tiene coste, es rápida de montarla, pero por contra hay determinadas acciones en las que no se comporta como una base de datos real. Levantar una base de datos en un contenedor (Docker). Aquí su principal ventaja es que el comportamiento exactamente igual que la base de datos, pero su principal inconveniente es que si lo montamos en una Build hay que sumar el tiempo de levantar el docker a la duración de la Build y dependiendo de que escenarios digamos que puede ser un stoper. Utilizar una base de datos \u0026ldquo;real\u0026rdquo; y luego bien usar alguna herramienta que nos deje la base de datos tal y como estaba inicialmente si estáis interesados en esta opción no me perdería la sesión que impartió Nacho Fanjul en la NetCoreConf Show me the Code # Una vez ya realizadas las aclaraciones sobre cómo vamos a librar algunos inconvenientes a la hora de realizar los Test, centrémonos en lo importante en el Test y como implementarlo.\nXUnit # La herramienta para realizarlos es xUnit, atrás han quedado los tiempos de MS-Test y .NET Core nos proporciona una librería de Test que si se utiliza correctamente es bastante potente.\nEn primer lugar, vamos a crearnos nuestro servidor de Test, para ello tendremos que crear un Startup que sera el punto de arranque. Lo que solomos hacer es crearnos un arranque que herede el StartUp que tenemos en la API y en dicho arranque lo que vamos a realizar es sobreescribir los métodos que vamos a realizar en los test.\npublic class TestStartup : Startup { public TestStartup(IConfiguration configuration, IWebHostEnvironment environment) : base(configuration, environment) { } } Tal y como tenemos montado el StartUp tenemos un método donde Configuramos la Autenticación y ahora para el test lo que vamos a realizar es indicarle que coja un esquema que está dentro del Test Server (en lugar del esquema que tenemos puesto en la API )\nprotected override void ConfigureAuthentication(IServiceCollection services) { services.AddMvc().AddApplicationPart(typeof(AvengerController).Assembly); services.AddAuthentication(options =\u0026gt; { options.DefaultScheme = TestServerDefaults.AuthenticationScheme; }) .AddTestServer(); } El código expuesto no tiene mucho misterio solo la primera línea que esto lo que hará es que cuando realicemos una petición usando el TestServer podamos invocar directamente a la clase del controlador en lugar de a la url que se levanta.\nUna vez tenemos resuelto el tema de la autenticación, vamos con la parte de la base de datos. En este caso vamos a utilizar un SQLLite que se genera en memoria, para ello pondremos el siguiente código\nprotected override void ConfigureBD(IServiceCollection services) { var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = \u0026#34;:memory:\u0026#34; }; var connectionString = connectionStringBuilder.ToString(); var connection = new SqliteConnection(connectionString); services .AddEntityFrameworkSqlite() .AddDbContext\u0026lt;AppDbContext\u0026gt;( options =\u0026gt; options.UseSqlite(connection) ); } Vale la base de datos ya la tenemos in Memory pero mucha veces de cara a los test es interesante que tengamos datos ya cargados de pruebas. Para ello lo que vamos a hacer uso de Bogus una librería que nos permite multitud de opciones para crearnos unos datos de test o de pruebas muy útiles. Para ello en el mismo método añadimos la siguiente funcionalidad para insertar los datos (se podría usar el Seed propio de EF pero si es algo que no lo vamos a tener en el código de producción a mí personalmente me gusta más separarlo por dejarlo más limpito)\nvar serviceProvider = services.BuildServiceProvider(); var storeContext = serviceProvider.GetRequiredService\u0026lt;AppDbContext\u0026gt;(); storeContext.Database.OpenConnection(); storeContext.Database.EnsureCreated(); storeContext.Database.Migrate(); if (!storeContext.Avenger.Any()) { var avengerList = new Faker\u0026lt;Avenger\u0026gt;().Generate(10); storeContext.AddRange(avengerList); storeContext.SaveChanges(); } Una de las cosas muy chulas que tiene xUnit es la posibilidad de inyectar a tus pruebas un estado previo en el que se encuentra, esto podemos decir que es el TestFixture. Nos crearemos un TestFixture en el que se creara el Test de Pruebas y también aquellos aspectos que serán necesarios para los test. En este caso yo me he creado una Claim que será la que se utilice para la autenticación.\npublic class TestFixture : IDisposable { public TestServer TestServer { get; private set; } public HttpClient Client { get; private set; } public string TenantIdClaimType =\u0026gt; \u0026#34;http://schemas.microsoft.com/identity/claims/tenantid\u0026#34;; public List\u0026lt;Claim\u0026gt; DefaultIdentity { get { return new List\u0026lt;Claim\u0026gt;() { new Claim(ClaimTypes.Name, \u0026#34;Hulk\u0026#34;), new Claim(TenantIdClaimType, \u0026#34;3ab8g5de-32c5-3134-zfdarf-182a67a344\u0026#34;), new Claim(ClaimTypes.Upn, \u0026#34;hulk@theavenger.com\u0026#34;) }; } } public TestFixture() { CreateServer(); } private void CreateServer() { var path = Assembly.GetAssembly(typeof(TestFixture)) .Location; Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() .MinimumLevel.Override(\u0026#34;Microsoft\u0026#34;, LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); var hostBuilder = new WebHostBuilder() .UseContentRoot(Path.GetDirectoryName(path)) .ConfigureServices(services=\u0026gt; { services.AddAutofac(); }) .UseSerilog() .ConfigureAppConfiguration(cb =\u0026gt; { cb.AddJsonFile(\u0026#34;appsettings.json\u0026#34;, optional: false) .AddEnvironmentVariables(); }) .UseStartup\u0026lt;TestStartup\u0026gt;(); this.TestServer = new TestServer(hostBuilder); this.Client = TestServer.CreateClient(); } public void Dispose() { if (Client != null) { Client.Dispose(); } if (TestServer != null) { TestServer.Dispose(); } } } Una vez ya tenemos creado el TestServer y el TestFixture vamos a lo que realmente importa los Test. En primer lugar y por convención cada uno de los controladores de nuestra API que vamos a testear los llamamos escenarios. Nos creamos una clase en la que le inyectemos la Fixture para ello tendremos algo similar al siguiente código.\n[Trait(\u0026#34;Category\u0026#34;, \u0026#34;Integration\u0026#34;)] public class AvengerScenarios : IClassFixture\u0026lt;TestFixture\u0026gt; { private readonly TestFixture fixture; public AvengerScenarios(TestFixture fixture) { this.fixture = fixture; } } Luego implementaremos el Test, por ejemplo, vamos a testear que el método que nos devuelve a todos los Avengers. Para ello nos tenemos que asegurar que esta autenticado, para ello usaremos el método WithIdentity que nos proporciona Acheve. Luego usaremos la librería Fluent Assertions el principal motivo es por legibilidad de los test que los hace mucho más similares al lenguaje humano.\n[Fact] public async Task Get_AllAvengers_returnOk() { var response = await fixture.TestServer .CreateHttpApiRequest\u0026lt;AvengerController\u0026gt;(controller =\u0026gt; controller.Get()) .WithIdentity(fixture.DefaultIdentity) .GetAsync(); response.StatusCode.Should().Be(HttpStatusCode.OK); } Resumen # En este articulo hemos visto el Tooling de herramienta que utilizo para los Test de Integración.\nAcheve.TestHost Bogus FluenAsserttions Microsoft.AspNetCore.TestHost Microsoft.EntityFramewokcore.SQlite Microsoft.EntityFramewokcore.InMemory xUnit\nEste tipo de Test, también se pueden hacer con herramientas como Postman sin embargo yo creo que desde Visual Studio y tal y como esta planteada esta forma nos aporta un suficiente valor por ejemplo de que cuando se realice algún cambio en algún método del controlador el Test directamente No compila, en cambio sí lo lanzamos desde un Postman la primera vez que lo lancemos nos devolverá un BadRequest y es un tiempo de cambio y de rehacer el test que no es tan fácil.\nJunto con estas herramientas hay alguna otra herramienta como Moq.Net, Coverlet que también utilizamos para otro tipo de Test o para obtener el porcentaje de cobertura de nuestros Test (aunque como muchas veces digo el porcentaje no es algo que me importe, sino más bien que tengamos testeadas las partes core de la aplicación).\n¿Y vosotros cual utilizáis? ¿Cuál echas de menos?\nHappy Codding\n","date":"22 junio 2020","externalUrl":null,"permalink":"/posts/tooling-testing-integration-dotnet/","section":"Posts","summary":"A la hora de empezar con los test de integración tenemos varias formas de realizarlos, en este artículo os enseñare las utilidades que utilizamos para hacer testing de integración desde Visual Studio y con DotNet Core.\n","title":"Que herramientas usamos para hacer test de integración en DotNet","type":"posts"},{"content":"","date":"22 junio 2020","externalUrl":null,"permalink":"/tags/testing/","section":"Tags","summary":"","title":"Testing","type":"tags"},{"content":"Hangfire es una librería en la que podemos ejecutar nuestros procesos en Background en nuestros desarrollos en .NET y .NET Core. En este articulo vamos a ver como poder utilizar la inyección que trae .NET Core de serie en nuestros proyectos y con esto nos evitamos el deber tener otro aspecto de configuración adicional, con esto nuestros procesos de Background son mucho más sencillo de configurar y sobre todo podemos testearlos al igual que cualquier otro elemento.\n¿Qué es Hangfire? # En las aplicaciones Web hay ocasiones en las que necesitamos tener algunas tareas en Background, que su ejecución no paralice el funcionamiento normal de nuestro desarrollo. Por ejemplos, un proceso de envóo de mails, un proceso de importación de datos, etc.. Para esto Hangfire es una librería que nos proporciona una serie de utilidades de serie como planificar cuando queremos lanzar este proceso (día, hora, periodicidad), también proporciona un marco de reintentos si algún proceso falla, se vuelve a lanzar\u0026hellip; Y por si fuera poco también se dispone de una interfaz Web en la que podemos ver todos los procesos que se tiene planificado y el resultado de las ejecuciones.\nSi sé que muchos lo estareis pensando y es que para todos estos procesos en un desarrollo Cloud en un entorno Microsoft para ello hago uso de un servicio serverless como son las Azure Functions. En cierta, forma las Azure Functions son muchos más que WebJobs con lo cual no estamos comparando el mismo servicio. Además vayamos a un escenario en el que tenemos una Web en la que por las noches no tiene tráfico y el servidor digamos que esta prácticamente parado por lo que disponemos de una franja en la que podamos aprovecharla para todas estas tareas sin la necesidad de adquirir un servicio extra (con un coste asociado al mismo). Como muchas veces digo para un determinado escenario no hay solo una opción válida sino que nos interesa conocer las alternativas y escoger la mejor (o la menos mala :P).\nPara aquellos que es la primera vez que escuchan hablar sobre Hangfire les recomiendo que vayan a leer esté artículo que escribió el gran Juan Carlos Martínez y luego continuar con la lectura de este artículo.\nComo funciona Hangfire dentro de una aplicación de .NET # Cuando instalamos HangFire en nuestra solución y le indicamos proyecto web que vamos a utilizar HangFire tenemos que hacer dos cosas:\nElegir la unidad de almacenamiento donde se va a residir toda la gestión de procesos. Esta unidad de almacenamiento puede ser desde un SQL Server, CosmosDB, MariaDb, Azure BlobStorage, etc El punto donde va a arrancar HangFire es decir por ejemplo http://avenger.com/hangfire Donde podemos ver todos los procesos que están en ejecución y los resultados obtenidos. Como podeis ver lo que hace Hangfire es montar otra aplicación independiente dentro de nuestro servidor Web aprovechando la misma infraestructura. Esto que provoca que sino se indica nada, toda inyección de dependencia que tengamos en .NET Core no la tendremos en los procesos que implementemos en HangFire. Cuando hablamos de inyección de dependencias todos estamos pensando en las interfaces de nuestra logica de negocio, etc.. pero hay muchas más cosas que se inyectan en .NET Core que van desde las propias settings de configuración hasta el sistema de Logs. Por lo que, aunque pensemos que quizás no me interese inyectarle las dependencias de mi proyecto a los procesos de HangFire porque un proceso rápido y eficaz y que solo se ejecuta una vez no requiere de inyección de dependencias, creo que estamos en un contexto diferente.\nNecesitamos inyección de dependencias en un proceso en HangFire # Si en lugar de poner HangFire pusiera Azure Functions, tendríamos las dos opiniones:\nGente partidaria de tener la inyección de dependencia PROS: Puedo testear mis métodos Contra: El arranque de un proceso cuya naturaleza sea breve, le estamos añadiendo un tiempo que en ocasiones es tiempo que necesitamos Gente en contra PRO: Simplicidad del proceso, velocidad de ejecución Contra: Testeable solo en un entorno Mi opinión en cuanto a Hangfire, es que el escenario en el que estamos no es el mismo que el que tenemos en una Functions, principalmente porque en nuestra aplicación arranca el servidor y hay carga todas las dependecias que tiene y las resuelve. El escenario no es el mismo y la ejecución tampoco. Por lo tanto, para mi aunque HangFire es un subproceso dentro de nuestro servidor en caso de que estos procesos requieran elementos del desarrollo (algo de lo más normal) debería de tener una inyección de dependencia para tener un ciclo de vida \u0026ldquo;normal\u0026rdquo; dentro del desarrollo.\nManos a la obra # Partimos de la base que ya tenemos creado un proyecto de Dotnet\ndotnet new webapi Agregaremos los paquetes de Nuget necesarios para hacer uso de Hangfire\ndotnet add package hangfire.core dotnet add package hangfire.aspnetcore dotnet add package hangfire.memorystorage Si todo ha ido correctamente y arrancamos la solución tendremos estos paquetes instalados El siguiente paso sería como en casi todo en Dotnet Core añadir los middleware necesarios para que arranque Hangfire.\npublic void ConfigureServices(IServiceCollection services) { services.AddHangfire(configuration =\u0026gt; configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseMemoryStorage()); services.AddHangfireServer(); services.AddControllers(); } En el Hangfire lo que hemos utilizado es establecer la configuración tal y como indica la página oficial de HangFire y en el ultimo puesto le hemos indicado que utilice como almacenamiento un almacenamiento en memoria. Para almacenar toda esta información existen multitudes de opciones como SQl Server, CosmosDB, Azure Blob Storage, etc.\nEn el método de configurar añadiremos que arranque un Dashboard para que tengamos una visualización de los procesos que se están ejecutando.\npublic void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseHangfireDashboard(); app.UseEndpoints(endpoints =\u0026gt; { endpoints.MapControllers(); }); } Si arrancamos nuestra aplicación y nos dirigimos a la ruta \u0026ldquo;/hangfire\u0026rdquo; visualizamos la siguiente pantalla: Por poner un caso de uso implementaremos un servicio de envío de mail diario. Para ellos nos creamos una carpeta que se llame \u0026ldquo;Services\u0026rdquo;, en esta carpeta tenemos por un lado un servicio con su interfaz que envía mail (en este ejemplo lo simula, ya que no es parte de este artículo). Tambien nos crearemos una interfaz para el servicio que vamos a implementar en Hangfire\nLa interfaz tendrá la siguiente estructura:\npublic interface ISendMailServices { Task\u0026lt;bool\u0026gt; SendMailAsync(string mail, string message); } y la implementación seria la siguiente:\npublic class SendMailServices : ISendMailServices { public async Task\u0026lt;bool\u0026gt; SendMailAsync(string mail, string message) { await Task.Delay(3000); return true; } } Ahora para el servicio que vamos a implementar en Hangfire tendremos la siguiente interfaz:\npublic interface IHangFireServices { Task ScheduleJob(); } public class HangFireServices : IHangFireServices { private readonly IConfiguration configuration; private readonly ISendMailServices sendMailServices; public HangFireServices(IConfiguration configuration,ISendMailServices sendMailServices ) { this.configuration = configuration; this.sendMailServices = sendMailServices; } public async Task ScheduleJob() { var email = this.configuration.GetSection(\u0026#34;sendmail\u0026#34;).Value; var message = \u0026#34;Hola q ase\u0026#34;; await this.sendMailServices.SendMailAsync(email, message); } } Tuneando el arranque # En primer lugar, creamos la clase \u0026ldquo;HangfireActivator\u0026rdquo; que depende de una clase JobActivator que es la que configura la inyección de dependencias para el servicio de Hangfire.\npublic class HangfireActivator : JobActivator { private readonly IServiceProvider serviceProvider; public HangfireActivator(IServiceProvider serviceProvider) { serviceProvider = serviceProvider; } public override object ActivateJob(Type jobType) { return serviceProvider.GetService(jobType); } } Una vez ya tenemos el desarrollo por un lado tendremos que inyectar las dependencias en nuestro StarUp para ello dentro del método \u0026ldquo;ConfigureServices\u0026rdquo; añadiremos las siguientes líneas de código.\nservices.AddTransient\u0026lt;ISendMailServices, SendMailServices\u0026gt;(); services.AddTransient\u0026lt;IHangFireServices, HangFireServices\u0026gt;(); Ahora en el método \u0026ldquo;Configure\u0026rdquo; en primer lugar añadiremos un nuevo parámetro IServiceProvider serviceProvider, luego añadiremos la siguiente linea para resolver la inyección de dependencia dentro de Hangfire\npublic void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) { GlobalConfiguration.Configuration.UseActivator(new HangfireActivator(serviceProvider)); Para finalizar ya podemos crear el Job de Hangfire con una línea como la siguiente:\nRecurringJob.AddOrUpdate\u0026lt;IHangFireServices\u0026gt;(hangfire =\u0026gt; hangfire.ScheduleJob(), Cron.Weekly); Si ahora ejecutamos el código y vamos a la pestaña \u0026ldquo;Tareas Recurrentes\u0026rdquo; observamos que tendremos programada la ejecución de la tarea que acabamos de añadir. Resumiendo # DotNet Core nos proporciona un sistema muy modular y eso hace que aplicaciones de tercero se puedan integrar de una manera fácil y sencilla. En este artículo hemos visto cómo hacerlo con Hangfire. Como habéis podido observar Hangfire es una librería muy potente y con multitud de opciones. Si lo tengo que reconocer por si no lo habéis notado aun Hangfire es algo que me gusta mucho :).\nPodéis ver el ejemplo en mi repositorio de Github\nHappy codding\n","date":"15 junio 2020","externalUrl":null,"permalink":"/posts/dependency-hangfire-dotnet/","section":"Posts","summary":"Hangfire es una librería en la que podemos ejecutar nuestros procesos en Background en nuestros desarrollos en .NET y .NET Core. En este articulo vamos a ver como poder utilizar la inyección que trae .NET Core de serie en nuestros proyectos y con esto nos evitamos el deber tener otro aspecto de configuración adicional, con esto nuestros procesos de Background son mucho más sencillo de configurar y sobre todo podemos testearlos al igual que cualquier otro elemento.\n","title":"Como inyectar las dependencias de Dotnet Core en nuestros procesos de Hangfire","type":"posts"},{"content":"","date":"15 junio 2020","externalUrl":null,"permalink":"/tags/hangfire/","section":"Tags","summary":"","title":"Hangfire","type":"tags"},{"content":"A la hora de empezar nuestros desarrollos en C# en la mayoria de los veces hay una serie de pasos manuales que se repiten proyecto tras proyecto y que nos hacen perder un poco de tiempo en diversas tareas repetitivas y que al final nos hacen perder un valioso tiempo. En este articulo vamos a ver como usar el sistema de templates que nos proporciona DotNet y como la podemos publicar en un repositorio Nuget para ser accessible por todos los miembros del equipo.\n¿Porqué debemos de tener un template? # En primer lugar esta es la primera pregunta que debemos de hacernos, el principal motivo que tiene que tener el template es ahorrarnos tiempo en tareas repetitivas y mecanicas. Este Template no debe de ser algo que tiene que ser válido para todos los proyectos (no hay balas de plata), sino que debe de incluir aquellos aspectos que estamos cansados de repetir proyecto tras proyecto, por ejemplo,\ntener un proyecto de Test en el que ya tenemos instalados un Test Server, el StartUp y las librerias que hacemos uso para dicho efecto tener configurado algunos middleware que hacemos uso si o si: inyección de dependencias, Problem Details, Swagger, versioning, configuración Esto quiere decir que si en alguno de dichos middleware quiero realizar alguna modificación no la podré hacer? La respuesta es que NO naturalmente es un template y nosotros somos desarrolladores y debemos de disponer de la creatividad necesaria para adaptarlo a nuestros requisitos y que todo lo que tenga el template no sea un stoper ni un impedimiento al equipo.\nQué beneficios nos proporciona # Al final una de las cosas más dificiles de conseguir en un equipo de desarrollo es que el sofware desarrollado sea del equipo y no sea un conjunto de piezas de cada uno de los developers que estan en el mismo. El tener un template no va a solucionar esto pero si que pone un esqueleto que puede ser la base en la que se fundamente el resto de la aplicación y si este template sirve para tener un marco de herramientas base para todos los proyecto creo que es un buen punto de partida y sobre el que debemos de poner los siguientes cimentos para conseguir el objetivo.\nManos a la obra # Crear la solución # En primer lugar tenemos que tener claro cual es el template que vamos a utilizar, en este ejemplo he creado una solución en la que hay montada una API, con diversos servicios. Se quedaria una solución como la siguiente:\nDe esta solución comentar que en el middeware de la API tenemos montado la configuración de diversos middleware que vamos a utilizar en dicha API: Problem Details, Swagger, Autofac, Autenticación con el Azure Active Directory, etc.. quedando de la siguiente forma:\nEn la parte de los test tenemos montado un ejemplo de como empezar a Testear todos los métodos que tiene el controlador, para ello hacemos uso diversas librerias que nos facilitan la vida como son TestServer, Acheve, FluenAssertions, Bogut y XUnit de tal forma que podamos escribir test de integración de una forma muy sencilla.\nEl contenido del template lo podeis ver en mi repositorio de GitHub,aunque no es contenido meternos en profundidad en dicha solución sino que hacemos hincapie en diversos artefactos que hemos añadido para poder entender los motivos que nos ha llevado a ello.\nAñadir la configuración que requiere DotNet # Una vez tenemos claro nuestro template deberemos de crear una carpeta .template.config y dentro de la misma un fichero template.json que deberia tener una estructura como la siguiente:\n{ \u0026#34;$schema\u0026#34;: \u0026#34;http://json.schemastore.org/template\u0026#34;, \u0026#34;author\u0026#34;: \u0026#34;Adrian Díaz\u0026#34;, \u0026#34;classifications\u0026#34;: [ \u0026#34;Web\u0026#34;, \u0026#34;WebAPI\u0026#34; ], \u0026#34;identity\u0026#34;: \u0026#34;Avenger.APITemplate.CSharp\u0026#34;, \u0026#34;groupIdentity\u0026#34;:\u0026#34;Avenger.APITemplate\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;ASP.NET Core Avenger Web API \u0026#34;, \u0026#34;shortName\u0026#34;: \u0026#34;avengerwebapi\u0026#34;, \u0026#34;tags\u0026#34;: { \u0026#34;language\u0026#34;: \u0026#34;C#\u0026#34;, \u0026#34;type\u0026#34;:\u0026#34;project\u0026#34; }, \u0026#34;sourceName\u0026#34;: \u0026#34;Avenger\u0026#34; } De dicho Json lo que debemos de tener claro:\nClassifications =\u0026gt; Cuando hacemos dotnet new nos muestra los distintos tipos de proyectos que hay agrupados por cada uno de los tipos de proyectos y sus distintas tipos ejemplo: Common/Console, Test/xUnit, Web/MVC/SPA, Solution, \u0026hellip; etc shortName =\u0026gt; el nombre que va a tener nuestra solución cuando hagamos el comando dotnet new .. tags =\u0026gt; Aqui deberemos de indicar cual es el lenguaje de programación en el que vamos acrear nuestra solución, en nustro caso C# sourceName =\u0026gt; Aqui se indica el valor que va a reemplazar en nuestra solución de todos los proyectos que tenemos, obteniendo el valor que se le pase por la linea de comando o bien la carpeta donde se encuetra de esta forma tendremos una nueva solución con los nombres estrablecidos. Una vez tenemos la configuración y el template el primer paso es instalarlo en nuestra maquina para poder comprobar que funciona correctamente para ello tendremos que hacer el siguiente comando:\ndotnet new -i {ruta donde se encuenta la carpeta .template.config} Si todo ha ido bien y ahora hacemos un dotnet new visualizaremos que dentro de las opciones disponibles tendremos la nueva solución que acabamos de crear.\nSi quremos desinstalar el template deberemos de ejecutar la siguiente instrucción\ndotnet new -u {ruta donde se encuenta la carpeta .template.config} Instalarla en un servidor de Nuget # Hemos creado la solución pero ahora el siguiente paso que debemos de hacer es añadirla en un servidor de paquetes como es Nuget para que todos los miembros del equipo la puedan utilizar. Para ello en primer lugar en la solucion deberemos de crear un fichero con extensión .nuspec que deberia de quedar de la siguiente forma:\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;utf-8\u0026#34;?\u0026gt; \u0026lt;package xmlns=\u0026#34;http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd\u0026#34;\u0026gt; \u0026lt;metadata\u0026gt; \u0026lt;id\u0026gt;Avenger.APITemplate.CSharp\u0026lt;/id\u0026gt; \u0026lt;version\u0026gt;1.0.0\u0026lt;/version\u0026gt; \u0026lt;description\u0026gt; Creates the Avenger API. \u0026lt;/description\u0026gt; \u0026lt;authors\u0026gt;Adrián Díaz\u0026lt;/authors\u0026gt; \u0026lt;packageTypes\u0026gt; \u0026lt;packageType name=\u0026#34;Template\u0026#34; /\u0026gt; \u0026lt;/packageTypes\u0026gt; \u0026lt;/metadata\u0026gt; \u0026lt;/package\u0026gt; Más o menos la única diferencia con un paquete de Nuget \u0026ldquo;clasico\u0026rdquo; es que en packageTypes deberemos de indicar que es de tipo Template.\nCon el fichero nuspec creado el siguiente paso ya es crear un proceso que nos automatice la publicación de dicho Template en el servidor de Nuget bien público o bien privado. Para ello una build sencilla con los siguientes pasos seria suficiente:\nUna ves esta la solución instalada en el servidor de Nuget lo único que haria falta seria que cada desarrollador se la instale en su maquina de desarrollo para ello con instalar el siguiente comando\ndotnet new -i avenger.apitemplate.csharp Resumen # La creación de una plantilla con multiples soluciones es algo bastante sencillo y que nos puede ahorrar mucho tiempo a la hora de empezar los desarrollos. Este tipo de plantilla se puede complicar todo lo que realmente necesitemos, por ejemplo si queremos añadir un tipo de proyectos o no, si quemos renombrar la solución, etc.. para ver toda las posiblidades que hay recomiendo que visualiceis el repo oficial de dotnet donde hay multitud de ejemplos.\nHappy Coding.\n","date":"8 junio 2020","externalUrl":null,"permalink":"/posts/template-dotnet-nuget/","section":"Posts","summary":"A la hora de empezar nuestros desarrollos en C# en la mayoria de los veces hay una serie de pasos manuales que se repiten proyecto tras proyecto y que nos hacen perder un poco de tiempo en diversas tareas repetitivas y que al final nos hacen perder un valioso tiempo. En este articulo vamos a ver como usar el sistema de templates que nos proporciona DotNet y como la podemos publicar en un repositorio Nuget para ser accessible por todos los miembros del equipo.\n","title":"Como crear un template Dotnet para nuestras soluciones","type":"posts"},{"content":"","date":"8 junio 2020","externalUrl":null,"permalink":"/tags/templates/","section":"Tags","summary":"","title":"Templates","type":"tags"},{"content":" Sobre mi # Mi nombre es Adrían Díaz Cervera, la verdad es que me gusta hablar de tecnología sobre cualquier plataforma y en cualquier rincón del mundo que se precie. Aunque tengo una parte muy de hogar y quizas esta es la parte que me implica a escribir en el blog, los circunstancias han cambiado últimamente por el tema del Covid-19 y todo eso hace quizas eche un poco de menos echar la vista atras y dejar constancia de todo lo acontecido en los últimos tiempos. También hay una parte en la que muchas veces no nos paramos a pensar y no es otra que el tener en cuenta como abordamos las soluciones, mostramos las últimas tecnologías pero nos perdemos en el día a día y el porque \u0026hellip;\nLlevo en la parte del desarrollo de sofware \u0026hellip; creo que casi 20 años y puedo decir que cada año me gusta más, igual queda un poco de abuelo cebolleta pero si miras como planteabamos los proyectos hace mucho tiempo y como los planteas ahora piensalo y mira la evolución y lo que te rondare morena.\nAhora mismo mi rol oficial es Responsable de Arquitectura en Encamina \u0026hellip; pero lo que realmente me gusta es ver crecer a las personas y como van evolucionando dentro de una organización, ver a gente que entra muy jovén y que sabes que van a ser muy muy buenos siempre les digo que no se olviden cuando sean ric@s y famos@s :)\nComunidad # Otra de mis pasiones es la comunidad, pero una comunidad un poco diferente (igual el de raro soy yo). Creo en que las personas no hacen las cosas por interes y porque les llena y con esa finalidad me uni a la organización de la NETCoreConf y a CATZure junto con mis hermanos Robert, Manu y Txema. La finalidad de hacer otro tipo de eventos y que se pueden hacer las cosas bien y sin tener intereses empresariales.\nPor otro lado, desde hace Enero del 2014 he sido nombrado por Microsoft como MVP Most Value Professional en diversas categorias: SharePoint Server, Office Server and Services, Office Development, Azure y actualmente en la categoria de Developer Technologies. Lo mejor del premio es el poder hablar con muchas gente de cualquier parte del mundo (siempre que mi ingles lo permite) sobre tecnología, además de poder hablar con diversos equipos de producto y saber cosas que nunca podré contar ..:)\n","date":"5 junio 2020","externalUrl":null,"permalink":"/about/","section":"Avenger Developer","summary":"Sobre mi # Mi nombre es Adrían Díaz Cervera, la verdad es que me gusta hablar de tecnología sobre cualquier plataforma y en cualquier rincón del mundo que se precie. Aunque tengo una parte muy de hogar y quizas esta es la parte que me implica a escribir en el blog, los circunstancias han cambiado últimamente por el tema del Covid-19 y todo eso hace quizas eche un poco de menos echar la vista atras y dejar constancia de todo lo acontecido en los últimos tiempos. También hay una parte en la que muchas veces no nos paramos a pensar y no es otra que el tener en cuenta como abordamos las soluciones, mostramos las últimas tecnologías pero nos perdemos en el día a día y el porque …\n","title":"About me","type":"page"},{"content":"La verdad es que hace mucho que suelo escribir en diversos blogs bien en la empresa donde trabajo ENCAMINA bien en la revista hispana Compartimoss, intente hacer un blog propio al principio en la denostada Geeks sin embargo por una cosa u otra no me termino llamando la atención. En el último año he escrito mucho menos de lo que hubiera querido, pero el tiempo no me ha dado la vida para más. Muchos más eventos en físico y muchas cosas que contar que al final solo queda la sesión, los preparativos y el resultado que con suerte queda grabado.\nPor todo esto queria tener un sitio donde me valiese a mi personalmente para poder tener un propio resumen de los trabajos en los que estoy participando y un poco contar mis impresiones sobre los eventos, actividades de la comunidad en las que participio y que quedase todo plasmado para que al paso de unos años mire atras y pueda decir te acuerdas de.. ? Sí me acuerdo y lo bien que lo pasamos :)\nSin más espero que tener pronto mi primer punto de partida y la primera entrada \u0026hellip; cualquier sugerencia o feedback es bienvenido. La semana que viene primera entrada de verdad\nHappy Codding!!\n","date":"5 junio 2020","externalUrl":null,"permalink":"/posts/init/","section":"Posts","summary":"La verdad es que hace mucho que suelo escribir en diversos blogs bien en la empresa donde trabajo ENCAMINA bien en la revista hispana Compartimoss, intente hacer un blog propio al principio en la denostada Geeks sin embargo por una cosa u otra no me termino llamando la atención. En el último año he escrito mucho menos de lo que hubiera querido, pero el tiempo no me ha dado la vida para más. Muchos más eventos en físico y muchas cosas que contar que al final solo queda la sesión, los preparativos y el resultado que con suerte queda grabado.\n","title":"Hello Blog","type":"posts"},{"content":"","date":"5 junio 2020","externalUrl":null,"permalink":"/tags/sobre-mi/","section":"Tags","summary":"","title":"Sobre Mi","type":"tags"},{"content":"","date":"5 junio 2020","externalUrl":null,"permalink":"/tags/welcome/","section":"Tags","summary":"","title":"Welcome","type":"tags"},{"content":"El SharePoint Saturday de Madrid siempre es un evento especial, muy especial, quizás fue mi primer gran evento hace muchos años y en Tajamar, cuando llegan las fechas de este evento siempre siempre tengo muchas ganas primero de tener sesión y segundo de pasarmelo bien. Como diria alguién que no me cae muy bien contigo empezo todo\u0026hellip; Este año ha tocado hacerlo en formato online\u0026hellip; creo que salvo que me equivoque todos los eventos que nos quedan este año seran en este formato (ojala me equivoque). En esta sesión junto mi gemelo Robert Bermejo creo que me gusto como quedo mucho mejor que la que di en la NetCoreConf, el darla junto a alguién siempre tienes la interacción con él y se puede hacer mucho más amena. Viendo la sesión otra vez creo que quedo muy chula, aunque igual falte un poco de contexto en alguna de las demos que vimos\n","date":"30 abril 2020","externalUrl":null,"permalink":"/posts/microsoft365-virtual/","section":"Posts","summary":"El SharePoint Saturday de Madrid siempre es un evento especial, muy especial, quizás fue mi primer gran evento hace muchos años y en Tajamar, cuando llegan las fechas de este evento siempre siempre tengo muchas ganas primero de tener sesión y segundo de pasarmelo bien. Como diria alguién que no me cae muy bien contigo empezo todo… Este año ha tocado hacerlo en formato online… creo que salvo que me equivoque todos los eventos que nos quedan este año seran en este formato (ojala me equivoque). En esta sesión junto mi gemelo Robert Bermejo creo que me gusto como quedo mucho mejor que la que di en la NetCoreConf, el darla junto a alguién siempre tienes la interacción con él y se puede hacer mucho más amena. Viendo la sesión otra vez creo que quedo muy chula, aunque igual falte un poco de contexto en alguna de las demos que vimos\n","title":"Microsoft 365 Virtual 2020","type":"posts"},{"content":"Otro de esos eventos que tienes marcado como de los grandes, es un evento fisicamente tuve la suerte de participar varias veces en Madrid, pero eso que un día recibes una whashap y te dice te apuntas no? y sabes que le dices no lo dudes \u0026hellip; seguro que sabéis de quien fue ese whatsapp, luego al año siguiente intente ayudar desde la distancia. Y este año este BootCamp en parte es una continuación de una NetCoreConf en la que se nos une Nacho Fanjul a ayudarnos \u0026hellip; pero otra vez el maldito COVID-19 que nos estropea lo planificado pero lo que no puede estropear son las ganas de hacer un evento y de manera global organizamos un evento online en el que se añadio el panel de expertos que fue uno de los grandes puntos fuertes. En esta evento tuve la suerte de participar en 4 sesiones \u0026hellip; un gran curro para grabar las charlas pero que merecio mucho la pena\nAzure Serverless : Expert Panel # Este panel de experto fue un Panel \u0026ldquo;NetCoreConf\u0026rdquo; en el que estabamos Manu Sanchez, Robert Bermejo, Nacho Fanjul, un servidor y Txema Balseiro haciendo las veces de moderador. Fue la primera sesión que grabe y tengo que decir que teniamos contenido para otra hora más minimo y lo que nos reimos y nos lo pasamos genial.\nConstruyendo Azure Apps con David Sánchez # Esta sesión fue diferente y el enfoque en el que participe también era diferente. Muchos sabreis quien es David Sánchez, un crack que trabaja en Microsoft\u0026hellip; si pero antes de que llegase a currar en Micro yo tuve la suerte de conocerle y hablar con él sobre tecnologia. Fue MVP de SharePoint Server como yo, una persona muy humilde y que me vale como espejo en el que mirarme alguién que esta triunfando por ser ser natural, humilde y buena persona y eso lo estamos perdiendo. Además de esto tuve la suerte de compartir la entrevista con mi compañero Santiago Porras momentos dificiles los que hemos pasado y muchas batallas en las que hemos luchado.\nAzure Databases: el panel de expertos # Lo bueno y lo que me llena de los eventos es la gran cantidad de buena gente que te encuentras en ellos. Esta sesión me toco ser el entrevistador y la verdad es que en primer lugar tenia mucho miedo el crear una serie de preguntas a los mayores especialistas de Base de Datos de España, en un terreno en el que no domino pues siempre piensas que si las preguntas son las que la gente que trabajo con Datos les hubiera echo. Un gran proder lleva una gran responsabilidad pero creo que fue otra hora de charla muy amena y constructiva en la que al estar rodeado de tanto crack imposible que algo no se pudiera aprender. Pero os diré una cosa lo mejor no es que sean cracks es que son buenas personas y mil gracias por uniros y entrar al trapo: Ruben Pertusa, Enrique Catalá, Jose Manuel Jurado y Miguel Egea\nEl bueno, el feo y el malo # Dejo para el final esta charla, era la segunda vez que la daba con Robert fue la última sesión que dimos antes del COVID-19. La hicimos en riguroso directo, en la que en algun momento pensabamos que aparecerian nuestros hijos por hay. Pero necesitabamos el feedback de estar con alguién allí. No grabar una sesión y posteriormente subirla\u0026hellip; no se el contacto en directo es algo que yo echo de menos, el hablar \u0026hellip; pero bueno ya volveremos a la normalidad.\n","date":"23 abril 2020","externalUrl":null,"permalink":"/posts/global-azure-virtual-2020/","section":"Posts","summary":"Otro de esos eventos que tienes marcado como de los grandes, es un evento fisicamente tuve la suerte de participar varias veces en Madrid, pero eso que un día recibes una whashap y te dice te apuntas no? y sabes que le dices no lo dudes … seguro que sabéis de quien fue ese whatsapp, luego al año siguiente intente ayudar desde la distancia. Y este año este BootCamp en parte es una continuación de una NetCoreConf en la que se nos une Nacho Fanjul a ayudarnos … pero otra vez el maldito COVID-19 que nos estropea lo planificado pero lo que no puede estropear son las ganas de hacer un evento y de manera global organizamos un evento online en el que se añadio el panel de expertos que fue uno de los grandes puntos fuertes. En esta evento tuve la suerte de participar en 4 sesiones … un gran curro para grabar las charlas pero que merecio mucho la pena\n","title":"Global Azure Virtual","type":"posts"},{"content":"Un SharePoint Saturday siempre es especial y este iba a ser Live mucho antes de la pandemia mi amigo Fernando Chiquiza me invitó mucho antes para participar. El evento era a nivel internacional y se celebraba en horario de Colombia, una sesión en riguroso directo, con los nervios de si algo se rompe o no se rompe (el efecto demo) y encima la hora eran los dos de la madrugada de España en el primer mes de un confinamiento y un sábado. El intentar no hablar muy alto para que mis peques no se despertasen. Algún amig@ desenadome suerte antes de empezar eso siempre da subidón y creo que aunque no fue la mejor charla de mi vida creo que fue un punto importante\n","date":"18 abril 2020","externalUrl":null,"permalink":"/posts/spslive2020-teams/","section":"Posts","summary":"Un SharePoint Saturday siempre es especial y este iba a ser Live mucho antes de la pandemia mi amigo Fernando Chiquiza me invitó mucho antes para participar. El evento era a nivel internacional y se celebraba en horario de Colombia, una sesión en riguroso directo, con los nervios de si algo se rompe o no se rompe (el efecto demo) y encima la hora eran los dos de la madrugada de España en el primer mes de un confinamiento y un sábado. El intentar no hablar muy alto para que mis peques no se despertasen. Algún amig@ desenadome suerte antes de empezar eso siempre da subidón y creo que aunque no fue la mejor charla de mi vida creo que fue un punto importante\n","title":"SPS-Live Como desarrollar en MS Teams y no morir en el intento!!","type":"posts"},{"content":"El primer evento durante el Covid, naturalmente todo de forma virtual, la nueva normalidad. Habíamos realizado eventos Online (WebCast, Charlas, etc..) pero eran eventos con una duración corta 1-1.5 horas a lo sumo. La afluencia a pesar de que Online se podría llegar a mucha más gente solía rondar entre 40-60 personas, este formato había decaído y por ese motivo se empezó a usar poco. Esta virtual surgió, tenemos que hacer algo pero algo que seamos nosotros y lo que decidimos es hacer tres tracks online en paralelo un sábado confinado y nuestra primera experiencia en Youtube emitiendo en un falso \u0026ldquo;directo\u0026rdquo; de esta forma mientras se estaba emitiendo la sesión el speaker podía estar contestando preguntas e interactuando con los asistentes. Cuando empezamos a primera hora de la mañana pensamos si somos 40 ya nos podemos tomar una copa de orujo, pero empezó la mañana desde pronto ya éramos casi 100 personas y en las horas puntas tuvimos 200 asistentes en directo \u0026hellip; En cuanto a mi sesión di la continuación de mi charla de API enfocando los temas de seguridad y autenticación. La verdad que no me gusta mucho grabarme pierdo esa interacción, el estar solo, el esperar que Marc no entre y salude a la cámara o ponga la tele te hace estar en muchas cosas y quizás pierdas el foco. Aquí tenéis la lista de reproducción y todas las sesiones que una vez más fue de lo bueno lo mejor :)\n","date":"4 abril 2020","externalUrl":null,"permalink":"/posts/netcoreconf-virtual-abril2020/","section":"Posts","summary":"El primer evento durante el Covid, naturalmente todo de forma virtual, la nueva normalidad. Habíamos realizado eventos Online (WebCast, Charlas, etc..) pero eran eventos con una duración corta 1-1.5 horas a lo sumo. La afluencia a pesar de que Online se podría llegar a mucha más gente solía rondar entre 40-60 personas, este formato había decaído y por ese motivo se empezó a usar poco. Esta virtual surgió, tenemos que hacer algo pero algo que seamos nosotros y lo que decidimos es hacer tres tracks online en paralelo un sábado confinado y nuestra primera experiencia en Youtube emitiendo en un falso “directo” de esta forma mientras se estaba emitiendo la sesión el speaker podía estar contestando preguntas e interactuando con los asistentes. Cuando empezamos a primera hora de la mañana pensamos si somos 40 ya nos podemos tomar una copa de orujo, pero empezó la mañana desde pronto ya éramos casi 100 personas y en las horas puntas tuvimos 200 asistentes en directo … En cuanto a mi sesión di la continuación de mi charla de API enfocando los temas de seguridad y autenticación. La verdad que no me gusta mucho grabarme pierdo esa interacción, el estar solo, el esperar que Marc no entre y salude a la cámara o ponga la tele te hace estar en muchas cosas y quizás pierdas el foco. Aquí tenéis la lista de reproducción y todas las sesiones que una vez más fue de lo bueno lo mejor :)\n","title":"NetCoreConf Virtual 2020","type":"posts"},{"content":"Que voy a decir de este evento\u0026hellip; hace mucho tiempo cuando empecé en los eventos tuve un sueño era que Valencia tuviera un evento grande de .NET y en la que estuvieran los mejores speakers de España y que además se llenase. Pues bueno decir que esos sueños a veces se hacen realidad y todo lo que había soñado salió mucho mejor de lo que hubiera pensado. A todo esto, si miramos la fecha fue el último evento \u0026ldquo;grande\u0026rdquo; que hubo en formato presencial, en aquel momento ninguno de nosotros esperábamos lo que los siguientes meses nos ha deparado. Un parón que nos hará tener más ganas cuando volvamos a los eventos presenciales\u0026hellip; el cuándo es la pregunta del millón. Más allá de la parte emocional, tuve el placer de dar una charla con Anna Almuni.\nPor cosas de la tecnología solamente se grabó el track 1 que lo podéis ver aquí.\n","date":"29 febrero 2020","externalUrl":null,"permalink":"/posts/netcoreconf-valencia2020/","section":"Posts","summary":"Que voy a decir de este evento… hace mucho tiempo cuando empecé en los eventos tuve un sueño era que Valencia tuviera un evento grande de .NET y en la que estuvieran los mejores speakers de España y que además se llenase. Pues bueno decir que esos sueños a veces se hacen realidad y todo lo que había soñado salió mucho mejor de lo que hubiera pensado. A todo esto, si miramos la fecha fue el último evento “grande” que hubo en formato presencial, en aquel momento ninguno de nosotros esperábamos lo que los siguientes meses nos ha deparado. Un parón que nos hará tener más ganas cuando volvamos a los eventos presenciales… el cuándo es la pregunta del millón. Más allá de la parte emocional, tuve el placer de dar una charla con Anna Almuni.\n","title":"NetCoreConf Valencia 2020","type":"posts"},{"content":"Empezamos el año y se empieza por todo lo alto en Barcelona (al igual que el año anterior), primer evento del año, mucha gente inscrita, el llegar a las 07:30 de la mañana al sitio del evento y ver ya gente esperando entrar y que te saluden\u0026hellip; es para flipar. Un pedazo de evento con una agenda simplemente espectacular. Aqui di una sesión sobre como implementar tu API Rest en .NET Core el podéis ver la charla que se grabó en el canal de YouTube de CatZure\nDe la charla la verdad es que aunque solo se ve la pantalla destacar que la sala estaba llena con la gente sentada en el suelo (algunos días cuando echo de menos los eventos miro la foto de porqué me gustan los eventos presenciales) y sobre todo que no me dio tiempo a terminarla, como me dicen me enrollo como las persianas.\n","date":"18 enero 2020","externalUrl":null,"permalink":"/posts/netcoreconf-barcelona2020/","section":"Posts","summary":"Empezamos el año y se empieza por todo lo alto en Barcelona (al igual que el año anterior), primer evento del año, mucha gente inscrita, el llegar a las 07:30 de la mañana al sitio del evento y ver ya gente esperando entrar y que te saluden… es para flipar. Un pedazo de evento con una agenda simplemente espectacular. Aqui di una sesión sobre como implementar tu API Rest en .NET Core el podéis ver la charla que se grabó en el canal de YouTube de CatZure\n","title":"NetCoreConf Barcelona 2020","type":"posts"},{"content":"En este apartado intentare poner todas las charlas que he ido dando, una breve explicación de la charla, material de esta y video si lo hubiera.\nConferencias 2024 # NetCoreConf 2024 Madrid NetCoreConf 2024 Barcelona Conferencias 2023 # NetCoreConf 2023 Barcelona Conferencias 2022 # NetCoreConf 2022 Barcelona NetCoreConf 2022 Madrid Kubernetes Day Spain - Como adoptar Kubernetes en una gran corporacion CollabDays Barcelona 2022 - Te entiendes con tu equipo por Teams SharePoint Saturday live - Docker for M365 Global Azure Spain 2022 - Evolución de tu desarrolla usando Azure Conferencias 2021 # [NetCoreConf Virtual 2021 Como el equipo A le salen casi siempre los planes bien](/posts/ netcoreconf-virtual-2021-equipo-a) Monitoring for Developers Conferencias 2020 # NetCoreConf Barcelona Buenas practicas para implementar tu API REST en .NET Core como un Avenger NetCoreConf Valencia Management para developers NetCoreConf Virtual Como autenticar y securizar tu API REST en .NET Core como un Avenger SPS Live - Como desarrollar en MS Teams y no morir en el intento!! Global Azure Virtual Microsoft 365 Virtual - Graph Toolkit Deep Dive Catzure - Azure Summer Talks Community NetCoreConf Virtual 2 Worskhop NetCoreConf Virtual 2 Arquitecturas de Alta Disponibilidad en Azure ","externalUrl":null,"permalink":"/charlas/","section":"Avenger Developer","summary":"En este apartado intentare poner todas las charlas que he ido dando, una breve explicación de la charla, material de esta y video si lo hubiera.\n","title":"","type":"page"},{"content":"","externalUrl":null,"permalink":"/search/","section":"Avenger Developer","summary":"","title":"Buscar","type":"page"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]