[{"content":"","date":null,"permalink":"/tags/claude/","section":"Tags","summary":"","title":"Claude"},{"content":" Stack: .NET 10 · C# 13 · SQLite · EF Core · xUnit · Swashbuckle · Claude Code · OpenSpec v1.3.1 · JetBrains Rider\nRepositorio: re-al-7/SSI.SSD.TodoApi\nFramework: openspec.dev\n¿Por qué Spec-First? #El flujo default de desarrollo con IA es prompt-driven: describes lo que quieres, el agente genera código, iteras. Funciona para tareas aisladas. Se rompe para cualquier cosa con superficie real — múltiples endpoints, reglas de dominio, concerns transversales. El agente no tiene memoria de decisiones previas, no conoce las restricciones arquitectónicas, y no puede distinguir \u0026ldquo;lo que se decidió\u0026rdquo; de \u0026ldquo;lo que acabo de asumir\u0026rdquo;.\nEl Spec-Driven Development (SDD) invierte esto. Primero escribes el contrato — qué debe hacer el sistema, qué no debe hacer, qué constituye éxito — y recién entonces le pides al agente que lo implemente. Las specs se \u0026ldquo;commitean\u0026rdquo; junto al código y se convierten en un documento vivo que sobrevive a los resets del context window.\n\u0026ldquo;Las specs no son documentación escrita después. Son la fuente de verdad que el agente lee antes de escribir cualquier cosa.\u0026rdquo;\nOpenSpec es un framework ligero que operacionaliza esta idea. No reemplaza tu IDE, tu lenguaje ni tu agente — agrega una capa de specs delante de ellos, con un CLI pequeño y slash commands que se integran directamente con Claude Code.\nLa Capa de Contexto — Antes de Cualquier Feature #La primera inversión es un conjunto de archivos de contexto que Claude Code lee al inicio de cada sesión. Son la memoria permanente del proyecto — lo que le dirías a cualquier integrante nuevo el primer día.\nTodoApi/ ├── CLAUDE.md ← comportamiento del agente, reglas TDD/SDD ├── .claude/ │ ├── settings.json ← comandos bash permitidos y denegados │ └── settings.local.json ← overrides personales (gitignored) └── openspec/ ├── CONTEXT.md ← stack, config de runtime, estructura del proyecto ├── STANDARDS.md ← naming, code style, prefijos de commit └── DATA-MODEL.md ← entidades, campos, relaciones Cada archivo tiene una sola responsabilidad:\nCONTEXT.md responde \u0026ldquo;¿qué estamos construyendo y cómo corre?\u0026rdquo; — versión de .NET, motor de base de datos, base path de la API, puerto. STANDARDS.md responde \u0026ldquo;¿cómo escribimos código?\u0026rdquo; — variables en camelCase, métodos en PascalCase, campos privados con _camelCase, patrón AAA en tests. DATA-MODEL.md es el esquema canónico: entidades, tipos de campos, constraints y relaciones. CLAUDE.md es específico de Claude Code — lo primero que lee el agente. Codifica el contrato de trabajo: siempre leer la spec antes de implementar, nunca escribir código sin un test que falle primero, usar ProblemDetails para todos los errores, commitear specs y código juntos. Lección aprendida: La configuración de runtime — puertos, launch profiles, appsettings.json — pertenece tanto a CONTEXT.md como a una specs/infrastructure/spec.md dedicada. En este proyecto, launchSettings.json y appsettings.json tuvieron que crearse manualmente porque no estaban declarados en ninguna spec. Si un archivo necesita existir para que el proyecto corra, necesita estar en una spec.\nFeature Specs — Just Enough, Just In Time #Las specs de features viven en openspec/specs/{feature}/. Cada feature recibe dos archivos: spec.md para requisitos y escenarios, y tests.md para el plan de tests correspondiente.\nImportante: No necesitás tener todas las specs escritas antes de escribir código. La regla es: escribí la spec y el plan de tests para el feature que vas a implementar ahora. Nada más.\nLas specs usan lenguaje estilo RFC — SHALL, SHALL NOT, MAY — y escenarios Given/When/Then:\n### Requisito: Crear lista El sistema SHALL permitir crear una lista de tareas con nombre. #### Escenario: Nombre duplicado - GIVEN una lista con el mismo nombre ya existe - WHEN se llama POST /api/v1/lists - THEN retornar 409 Conflict El tests.md companion mapea cada escenario a un test nombrado antes de que exista una sola línea de implementación:\n### Escenario: Nombre duplicado - [ ] `CreateList_DuplicateName_Returns409` - [ ] `CreateList_DuplicateName_DoesNotCreateRecord` Este es el punto de integración con TDD. Los nombres de tests en tests.md se convierten en los nombres de métodos en tests/TodoApi.Tests/. El agente escribe los tests que fallan primero, luego implementa hasta que pasen.\nArchivo Responde a Se escribe cuando CONTEXT.md ¿Qué construimos y cómo corre? Una vez, al inicio STANDARDS.md ¿Cómo escribimos código? Una vez, al inicio DATA-MODEL.md ¿Cómo es el esquema? Antes del primer feature, se actualiza según necesidad specs/{feature}/spec.md ¿Qué debe hacer este feature? Antes de implementar el feature specs/{feature}/tests.md ¿Cómo verificamos cada escenario? Antes de implementar el feature El Ciclo de Implementación #OpenSpec introduce tres slash commands que mapean directamente al ciclo SDD. Los tres corren dentro de una sesión de Claude Code (claude) desde la raíz del proyecto.\nCiclo completo por feature:\n/opsx:propose — Claude lee las specs y genera un directorio de change con proposal.md, design.md y tasks.md Revisar el proposal en Rider antes de aplicar — verificar scope, decisiones arquitectónicas, archivos a modificar /opsx:apply — Claude implementa las tasks, escribiendo los tests que fallan primero, luego la implementación Verificar — dotnet build, dotnet test, dotnet run /opsx:archive — el change se archiva y los delta specs se mergean en el árbol principal de specs El proposal.md no es una formalidad — es donde se detectan desalineaciones antes de que se conviertan en código.\nLa Revisión del Proposal — Un Ejemplo Real #Al implementar la spec todo-errors, el propose inicial fue:\n/opsx:propose \u0026#34;Global error handling middleware con ProblemDetails RFC 7807\u0026#34; Claude generó un proposal razonable — pero el proposal.md contenía esta línea:\n\u0026ldquo;Controllers continue to return NotFound() directly where applicable — the middleware handles the exception path.\u0026rdquo;\nEsto es la Opción B: dos code paths para errores 404. La spec todo-errors exige un contrato ProblemDetails uniforme para todos los tipos de error, lo que requiere la Opción A — los services lanzan NotFoundException, el middleware la captura, y los controllers no tienen lógica de null-check en absoluto.\nLa corrección fue reemplazar el proposal con una intención más explícita:\n/opsx:propose \u0026#34;Global error handling middleware con ProblemDetails RFC 7807 — los services deben lanzar NotFoundException en lugar de retornar null, controllers no deben retornar NotFound() directamente\u0026#34; Regla para el texto del propose: Si ya está en la spec, no necesitás repetirlo. Si no está en la spec pero tenés una preferencia arquitectónica específica, agrégalo al propose — o mejor, actualizá la spec primero. El texto del propose es para intención y constraints; la spec es para requisitos y escenarios.\nEl proposal resultante fue sustancialmente diferente. Claude refactorizó ambas interfaces de service para eliminar los nullable returns, reescribió las implementaciones para lanzar excepciones en lugar de retornar null, simplificó los controllers eliminando todo el boilerplate de null-check, y actualizó los tests de service existentes para hacer assert de NotFoundException en lugar de null. El cambio tocó nueve archivos — todos correctamente.\nEstructura Final del Proyecto #SSI.SSD.TodoApi/ ├── CLAUDE.md ├── TodoApi.sln ├── .claude/ │ ├── settings.json │ ├── settings.local.json │ └── skills/ │ ├── openspec-propose/ │ ├── openspec-apply-change/ │ ├── openspec-archive-change/ │ ├── openspec-explore/ │ └── openspec-sync-specs/ ├── openspec/ │ ├── config.yaml │ ├── CONTEXT.md │ ├── DATA-MODEL.md │ ├── STANDARDS.md │ ├── changes/archive/ ← 3 ciclos completados │ └── specs/ │ ├── infrastructure/ spec.md tests.md │ ├── todo-list/ spec.md tests.md │ ├── todo-item/ spec.md tests.md │ └── todo-errors/ spec.md tests.md ├── src/ │ ├── Controllers/ │ │ ├── ListsController.cs │ │ └── ItemsController.cs │ ├── Services/ │ │ ├── IListService.cs ListService.cs │ │ └── IItemService.cs ItemService.cs │ ├── Models/ │ │ ├── TodoList.cs │ │ └── TodoItem.cs │ ├── DTOs/ │ │ ├── CreateListRequest.cs CreateItemRequest.cs │ │ ├── UpdateListRequest.cs UpdateItemRequest.cs │ │ ├── ListResponse.cs ListDetailResponse.cs │ │ └── ItemResponse.cs │ ├── Data/ │ │ ├── AppDbContext.cs │ │ └── Migrations/ │ ├── Middleware/ │ │ └── ErrorHandlingMiddleware.cs │ ├── Exceptions.cs │ └── Program.cs └── tests/TodoApi.Tests/ ├── Lists/ │ ├── ListServiceCreateTests.cs │ ├── ListServiceGetAllTests.cs │ ├── ListServiceGetByIdTests.cs │ ├── ListServiceUpdateTests.cs │ └── ListServiceDeleteTests.cs ├── Items/ │ └── ItemServiceTests.cs └── Middleware/ └── ErrorHandlingMiddlewareTests.cs Resultado de tests al final de los tres ciclos:\ntotal: 24+ failed: 0 succeeded: 24+ skipped: 0 Build succeeded in 4.1s Qué Hace Realmente OpenSpec #Vale ser precisos sobre qué aporta OpenSpec vs qué aporta Claude Code. OpenSpec no escribe código. No analiza tu codebase. Genera skills — archivos Markdown estructurados en .claude/skills/ — que Claude Code detecta automáticamente.\nCada skill es un conjunto de instrucciones que le dice al agente cómo comportarse durante una fase específica del workflow. El skill openspec-propose dice: lee el contexto del proyecto, lee la spec relevante, produce un proposal con esta estructura. El skill openspec-apply-change dice: lee las tasks, implementa en orden, corré los tests después de cada cambio.\nEl CLI (openspec init, openspec config, openspec update) administra qué skills se generan según tu perfil:\nPerfil Workflows disponibles core propose explore apply archive custom + sync new continue ff verify bulk-archive onboard Nota sobre /opsx:sync: En OpenSpec v1.3.1, sync aparece en la documentación del core profile pero no se instala con openspec config profile core seguido de openspec update. Para obtenerlo usá el perfil custom: openspec config profile custom --workflows propose,explore,apply,sync,archive y luego openspec update --force.\nEl openspec/config.yaml #Más allá de los archivos Markdown de contexto, OpenSpec soporta un openspec/config.yaml que inyecta contexto del proyecto directamente en cada invocación de skill. Es una alternativa más limpia a escribir contexto en Markdown plano — el agente lo lee automáticamente sin necesitar una referencia en CLAUDE.md:\n# openspec/config.yaml schema: spec-driven context: | Platform: .NET 10 Language: C# 13 Database: SQLite via EF Core API base path: /api/v1 Port: 5020 Error format: ProblemDetails RFC 7807 rules: proposal: - Identificar archivos a modificar, no solo a crear - Indicar si los tests existentes necesitan actualizarse specs: - Usar SHALL/SHALL NOT para requisitos - Usar Given/When/Then para escenarios tasks: - Incluir una task final de verificación con dotnet test La Integración SDD + TDD #SDD y TDD son complementarios, no competitivos. La spec define el qué. El plan de tests en tests.md define cómo verificar el qué. Los archivos .cs de test son la implementación de ese plan. El código de producción hace pasar los tests.\nspec.md → tests.md → *Tests.cs (red) → implementación → *Tests.cs (green) La convención de naming en tests.md importa. Los nombres de tests siguen el patrón MethodName_Scenario_ExpectedResult:\nCreateList_DuplicateName_Returns409 DeleteList_WithTasks_DeletesListAndAllTasks Middleware_UnhandledException_DoesNotExposeDetail Estos nombres vienen directamente de los escenarios de la spec, creando una línea trazable desde el requisito hasta el test hasta la implementación.\nTradeoffs y Cuándo Funciona Mejor #SDD con OpenSpec agrega trabajo upfront. Escribir specs, planes de tests y archivos de contexto antes de tocar código es más lento que promptear directamente — en la primera hora. A lo largo de la vida de un proyecto, se recupera con creces: comportamiento predecible del agente, cero momentos de \u0026ldquo;¿cuál era la decisión arquitectónica acá?\u0026rdquo;, y la capacidad de pasarle contexto a una sesión nueva de Claude Code sin tener que re-explicar nada.\nEste approach funciona mejor cuando:\nTienes múltiples features relacionados con reglas de dominio compartidas El proyecto abarca múltiples sesiones (el agente olvida; las specs no) Hay más de un desarrollador involucrado y la consistencia importa Estás refactorizando — los delta specs describen qué cambia sin reescribir todo Agrega menos valor en:\nScripts verdaderamente aislados Transformaciones de datos one-off Cualquier cosa donde el contexto completo entra cómodamente en un solo prompt Código Fuente #El proyecto completo — incluyendo todas las specs, archivos de contexto, código generado y tests — está disponible en github.com/re-al-7/SSI.SSD.TodoApi.\nEl framework OpenSpec está en openspec.dev.\n","date":"May 7, 2026","permalink":"/2026/api-net-10-con-spec-driven-development/","section":"Posts","summary":"Stack: .NET 10 · C# 13 · SQLite · EF Core · xUnit · Swashbuckle · Claude Code · OpenSpec v1.3.1 · JetBrains Rider\nRepositorio: re-al-7/SSI.SSD.TodoApi\nFramework: openspec.dev\n¿Por qué Spec-First? #El flujo default de desarrollo con IA es prompt-driven: describes lo que quieres, el agente genera código, iteras. Funciona para tareas aisladas. Se rompe para cualquier cosa con superficie real — múltiples endpoints, reglas de dominio, concerns transversales. El agente no tiene memoria de decisiones previas, no conoce las restricciones arquitectónicas, y no puede distinguir \u0026ldquo;lo que se decidió\u0026rdquo; de \u0026ldquo;lo que acabo de asumir\u0026rdquo;.","title":"Construyendo una API .NET 10 con Spec-Driven Development, OpenSpec y Claude Code"},{"content":"","date":null,"permalink":"/tags/netcore/","section":"Tags","summary":"","title":"Netcore"},{"content":"","date":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts"},{"content":"","date":null,"permalink":"/","section":"Re-Al Dev World","summary":"","title":"Re-Al Dev World"},{"content":"","date":null,"permalink":"/tags/sdd/","section":"Tags","summary":"","title":"Sdd"},{"content":"","date":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"/tags/markdown/","section":"Tags","summary":"","title":"Markdown"},{"content":"Hay una brecha entre donde vive tu información y donde quisieras pensar con ella. Los correos de Outlook quedan atrapados en el cliente. Los PDFs son archivos planos que no se pueden enlazar. Las hojas Excel no conversan con tus notas. MD Converter cierra esa brecha: convierte cualquier documento —incluyendo hilos de correo completos— a Markdown limpio, estructurado y listo para herramientas como Obsidian.\nEl problema que resuelve #Si usas una herramienta de gestión de conocimiento personal (PKM) basada en Markdown, sabes el dolor: tus correos más importantes viven en Outlook, tus reportes en Word, tus datos en Excel. Copiar manualmente ese contenido es tedioso y pierde estructura. Lo que necesitas es un conversor que entienda el contexto de cada formato, no solo extraiga texto crudo.\nMD Converter es exactamente eso. No es un extractor genérico —es un sistema con reglas específicas por formato que produce Markdown listo para indexar, buscar y enlazar.\nDos formas de usarlo #Interfaz web local — Un servidor Flask corre en localhost:5000. Arrastrás archivos, los soltás, y en segundos aparecen los .md descargables. El watcher de carpeta va un paso más allá: apuntás a una carpeta (por ejemplo, donde Outlook guarda archivos .msg) y cualquier archivo nuevo se convierte automáticamente, sin intervención.\nCLI — Para uso programático o por lotes: #python convert_to_md.py reporte.pdf python convert_to_md.py correo.msg -o md_output/ python convert_to_md.py carpeta_de_correos/ # convierte todo python convert_to_md.py https://ejemplo.com\nFormatos soportados # Formato Librería Qué produce .docx mammoth Markdown con encabezados y listas preservados .pdf pdfplumber Texto extraído por página .pptx python-pptx Diapositivas como secciones Markdown .xlsx / .csv pandas + tabulate Tablas Markdown .html / URL html2text + BeautifulSoup Markdown con tablas .eml Biblioteca estándar Correo con frontmatter YAML .msg extract-msg Correo Outlook con soporte de hilos El módulo de correos: donde está la mayor inteligencia #El caso más complejo —y más valioso— es el de los correos de Outlook. Un .msg típico no es un mensaje: es un hilo entero de respuestas encadenadas. MD Converter lo descompone en mensajes individuales, cada uno con su propio archivo .md y metadatos precisos.\nSeparación de hilos (converters/email/thread.py) #El módulo _split_thread reconoce cinco patrones de separador distintos, cubriendo los formatos más comunes que generan Outlook Desktop, Outlook Web y Gmail:\n________________________ (línea de subrayados — Outlook Desktop) On [fecha] [persona] wrote: (Gmail / Outlook Web) \u0026mdash; Original Message \u0026mdash; (clientes alternativos) Bloque De: + Enviado: standalone (texto plano) From: + Sent: (cuerpo HTML convertido por html2text) Si ningún patrón coincide, el módulo cae en un modo de fallback que detecta bloques citados con \u0026gt; al estilo de clientes Unix. El resultado es siempre una lista de segmentos con cuerpo limpio y metadatos propios.\nCada segmento recibe su fecha real —no la del correo más reciente del hilo, sino la del mensaje original. Esto es fundamental para que los archivos queden ordenados correctamente en el sistema de notas.\nLimpieza de ruido (_clean_msg_segment) #Los correos corporativos vienen cargados de disclaimers, avisos automáticos de Microsoft, firmas redundantes y URLs de imágenes embebidas que no tienen sentido fuera del cliente. _clean_msg_segment aplica una lista de patrones de ruido configurables y los elimina antes de escribir el archivo:\nNOISE = [ re.compile(r\u0026#39;No suele recibir correo electrónico de\u0026#39;, re.IGNORECASE), re.compile(r\u0026#39;This e-?mail and any attachments? are confidential\u0026#39;, ...), re.compile(r\u0026#39;Imprime sólo si es necesario\u0026#39;, ...), # ... más patrones ] Además elimina indentación de tabs en texto citado, colapsa líneas en blanco excesivas y limpia referencias cid: de imágenes embebidas.\nTablas HTML en correos #Cuando el cuerpo HTML de un correo contiene tablas, html2text normalmente las aplana. El módulo converters/html.py incluye _html_to_md_with_tables, que preprocesa las tablas HTML antes de convertir el resto del cuerpo, generando tablas Markdown válidas. Para .msg, el sistema procesa en paralelo el cuerpo de texto plano (para detectar separadores de hilo) y el cuerpo HTML (para preservar tablas), y los empareja segmento a segmento cuando los conteos coinciden.\nFrontmatter YAML: estructura lista para Obsidian #Cada archivo de correo generado incluye un frontmatter YAML completo y consistente:\n--- fecha: 2026-03-15 de: \u0026#34;[[Alonzo Vera]]\u0026#34; para: - \u0026#34;[[Clara Cabrera]]\u0026#34; - \u0026#34;[[José Revollo]]\u0026#34; cc: - otro@empresa.com asunto: Revisión del contrato tipo: correo direccion: enviado tags: - correo adjuntos: - contrato_v2.pdf --- Los campos de, para y cc se resuelven automáticamente contra el sistema de aliases antes de escribirse.\nReglas configurables: contact_aliases.json #Este es uno de los puntos más potentes del sistema. En lugar de que los correos queden con cadenas de texto como \u0026ldquo;Alonzo Vera alonzo.vera@empresa.com\u0026rdquo; en los metadatos, el sistema las convierte a wikilinks de Obsidian: \u0026ldquo;[[Alonzo Vera]]\u0026rdquo;.\nLas reglas se definen en un archivo JSON en la raíz del proyecto:\n{ \u0026#34;aliases\u0026#34;: [ { \u0026#34;alias\u0026#34;: \u0026#34;\\\u0026#34;[[Alonzo Vera]]\\\u0026#34;\u0026#34;, \u0026#34;match\u0026#34;: [\u0026#34;Alonzo Vera\u0026#34;, \u0026#34;Alonzo.Vera\u0026#34;, \u0026#34;alvera\u0026#34;] }, { \u0026#34;alias\u0026#34;: \u0026#34;\\\u0026#34;[[Clara Cabrera]]\\\u0026#34;\u0026#34;, \u0026#34;match\u0026#34;: [\u0026#34;Clara Cabrera\u0026#34;, \u0026#34;Clara.Cabrera\u0026#34;] } ] } Cada regla tiene:\nmatch: lista de fragmentos a buscar (case-insensitive, busca subcadena en el campo completo) alias: el valor de reemplazo, en formato wikilink para Obsidian El archivo se lee en caliente con cada conversión —no requiere reiniciar el servidor. Agregar un contacto nuevo al JSON tiene efecto inmediato en la próxima conversión.\nEl sistema también detecta automáticamente si un correo fue enviado o recibido buscando los patrones del dueño del sistema (alonzo.vera, alvera) en el campo remitente, y escribe el campo direccion: enviado o direccion: recibido según corresponda.\nArquitectura modular #El proyecto está organizado para que cada formato sea independiente:\nconverters/ ├── docx.py — convert_docx ├── pdf.py — convert_pdf ├── html.py — convert_html (+ helpers de tablas) ├── tabular.py — convert_xlsx, convert_csv └── email/ ├── thread.py — lógica de separación de hilos ├── builders.py — construcción de frontmatter y Markdown ├── eml.py — convert_eml └── msg.py — convert_msg convert_to_md.py actúa como dispatcher: detecta la extensión del archivo y delega al conversor correcto. La UI Flask importa desde ese mismo dispatcher, por lo que CLI y web siempre usan exactamente la misma lógica de conversión.\nPara qué sirve en la práctica # Archivo de correos corporativos: cada hilo de Outlook se convierte en archivos individuales con fecha real, remitente resuelto como wikilink, y adjuntos listados en frontmatter. Ingestión de documentación: PDFs, Word y presentaciones de reuniones pasan directamente a la base de conocimiento. Pipeline automatizado: el watcher convierte los .msg que Outlook arrastra a una carpeta de salida, sin intervención manual. Búsqueda transversal: con todo en Markdown plano, cualquier herramienta (Obsidian, ripgrep, VS Code) puede buscar en correos, documentos y notas al mismo tiempo. Lo que queda configurable sin tocar código\nQué Dónde Efecto Aliases de contactos contact_aliases.json Resuelve nombres en frontmatter Carpeta vigilada UI web / POST /watch/start Define qué carpeta monitorear Carpeta de salida Argumento -o en CLI Destino de los .md generados Patrones de ruido _clean_msg_segment en thread.py Qué líneas se eliminan del cuerpo El proyecto está pensado como infraestructura personal: corre local, no necesita internet, no tiene base de datos, y produce archivos de texto plano que duran décadas. La inteligencia está en las reglas, y las reglas son editables.\nPuedes acceder a su repo público en GitHub.\n","date":"April 8, 2026","permalink":"/2026/md-converter-segunda-mente/","section":"Posts","summary":"Hay una brecha entre donde vive tu información y donde quisieras pensar con ella. Los correos de Outlook quedan atrapados en el cliente. Los PDFs son archivos planos que no se pueden enlazar. Las hojas Excel no conversan con tus notas. MD Converter cierra esa brecha: convierte cualquier documento —incluyendo hilos de correo completos— a Markdown limpio, estructurado y listo para herramientas como Obsidian.\nEl problema que resuelve #Si usas una herramienta de gestión de conocimiento personal (PKM) basada en Markdown, sabes el dolor: tus correos más importantes viven en Outlook, tus reportes en Word, tus datos en Excel.","title":"MD Converter: Tu bandeja de entrada y tus documentos como segunda mente"},{"content":"","date":null,"permalink":"/tags/obsidian/","section":"Tags","summary":"","title":"Obsidian"},{"content":"","date":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python"},{"content":"","date":null,"permalink":"/tags/congo/","section":"Tags","summary":"","title":"Congo"},{"content":"Ha pasado un buen tiempo desde el último post por aquí. La vida, los proyectos y el trabajo del día a día fueron relegando este espacio. Pero la intención de compartir lo aprendido nunca desapareció del todo.\nAsí que: volvemos.\nNueva casa, mismo espíritu #Como señal de que esto va en serio, el sitio ha pasado por una reestructuración completa. Lo que antes era un blog en Jekyll, ahora corre sobre Hugo con el tema Congo.\nEl cambio no es solo estético. Hugo es considerablemente más rápido para generar el sitio, Congo ofrece una experiencia de lectura limpia y moderna, y el flujo de publicación ahora está completamente automatizado via GitHub Actions — cada commit a master despliega el sitio en segundos.\nAlgunos de los cambios visibles:\nModo oscuro con paleta de colores Dracula Búsqueda integrada en el sitio Tabla de contenidos en posts largos Tiempo de lectura estimado Botones para compartir en redes sociales Comentarios con Disqus Qué viene #La idea es retomar el ritmo de publicación con temas que han ido acumulándose: .Net, PostgreSQL, DevOps, herramientas de desarrollo y todo lo que vaya surgiendo.\nNos leemos pronto.\n","date":"April 4, 2026","permalink":"/2026/de-vuelta...-y-con-nueva-casa/","section":"Posts","summary":"Ha pasado un buen tiempo desde el último post por aquí. La vida, los proyectos y el trabajo del día a día fueron relegando este espacio. Pero la intención de compartir lo aprendido nunca desapareció del todo.\nAsí que: volvemos.\nNueva casa, mismo espíritu #Como señal de que esto va en serio, el sitio ha pasado por una reestructuración completa. Lo que antes era un blog en Jekyll, ahora corre sobre Hugo con el tema Congo.","title":"De vuelta... y con nueva casa"},{"content":"","date":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo"},{"content":"","date":null,"permalink":"/tags/meta/","section":"Tags","summary":"","title":"Meta"},{"content":"","date":null,"permalink":"/tags/test/","section":"Tags","summary":"","title":"Test"},{"content":"En el anterior post usabamos Selenium para grabar la funcionalidad de una pag;ína web y luego automatizar las pruebas. En esta ocasión vamos a usar JMeter Recording Controller para grabar las peticiones HTTP que se realizan a un sitio y con ello simular la navegación de un usuario.\nEste tipo de escenarios nos puede servir para realizar pruebas de carga o estrés a nuestro servidor de aplicaciones. Al ejecutar peticiones HTTP, sin necesidad de una UI (como chromiun o gecko), podemos enviar varios ciclos de peticiones al mismo tiempo, sin necesidad de ver afectada la memoria de nuestro cliente.\nPrimero necesitamos instalar JMeter y crear un nuevo proyecto a partir de un Template:\nEn la ventana emergente, elegimos la opción Recording with Think Time y presionamos el botón Create:\nEste template nos creará la siguiente estructura de proyecto\nAhora añadimos los siguientes elementos al proyecto, dentro del elemento Thread Group añadir:\nAdd \u0026amp;rarr; Listener \u0026amp;rarr; Summary Report Add \u0026amp;rarr; Listener \u0026amp;rarr; View Results Tree Add \u0026amp;rarr; Listener \u0026amp;rarr; Simple Data Writer Tambien, dentro de TestPlan añadimos:\nAdd \u0026amp;rarr; Config Element \u0026amp;rarr; CSV DataSet Config Con todo ello, la estructura de nuestro proyecto debe verse asi:\nAntes de comenzar con la grabación, en el elemento HTTP(S) Test Script Recorder debemos definir algunos parametros, como ser: Port y HTTPS domains, además de un identificador Transaction Name que nos sservirá para diferenciar por grupos las peticiones a realizar (En nuestro ejemplo, vamos a usar el texto LOGIN). Debe quedar asi:\nAhora presionamos el botón Start. JMeter nos mostrará un mensaje sobre la creación de un Certificado que debe instalarse en nuestro navegador web:\nEste certificado se encuentra en el directorio bin dentro de la carpeta donde se ejecutó JMeter:\nEn nuestra prueba, vamos a instalarlo en Mozilla Firefox, para ello vamos al menpú Ajustes para importar el nuevo certificado:\nUna vez importado el certificado, debemos cambiar la configuración del PROXY de Mozilla Firefox para que todas las solicitudes HTTP sean enviadas a JMeter y puedan ser grabadas:\nCon todo esto realizado, podemos comenzar la grabación de nuestro caso de prueba, a través del panel Recorder: Transactions Control:\nEste panel nos permitirá añadir etiquetas (1), e incluso reiniciar los contadores de las etiquetas (2) para que podamos separar la ejecución de nuestra prueba en etapas.\nAl terminar de grabar el caso de prueba, presionamos el boton STOP del panel Recorder: Transactions Control. Nos fijamos en el elemento Recording Controller de nuestro proyecto en JMeter, y deberíamos tener algo asi:\nAl revisar cada petición HTTP realizada en nuestra grabación nos encontraremos con este tipo de detalles sobre datos de formulario:\nEstos valores podemos parametrizarlos a través del elemento CSV DataSet Config que se encuentra en nuestro proyecto.\nPara ello, creamos un archivo CSV (sin cabeceras y separado por comas), que tenga, por ejemplo, los siguientes valores:\nLa columna 1 se refiere al nombre de usuario\nLa columna 2 se refiere a la contraseña de inicio de sesión del usuario\nLa columna 3 se refiere al productId que deseamos añadir a nuestro carrito de compra\nLa columna 4 se refiere al SKU del producto seleccionado; esto nos servirá para simular tráfico a través de la URL\nEn JMeter, la configuración del elemento CSV DataSet Config debe quedar mas o menos asi:\nEs importante definir los nombres de las columnas en MAYUSCULAS, debido a que eso definirá los nombres de nuestras variables.\nPara usar una variable en el script que se ha creado, se usa la siguiente sintaxis:\n${NOMBRE_VARIABLE} Por lo que, nuestra petición de LOGIN, se verá asi:\nTambién se puede usar la nomenclatura de nombre de variables en las URLs:\nFinalmente, para usar todas las variables de nuestro archivo CSV, y que JMeter vaya iterando cada registro, se debe realizar la siguiente configuración en el elemento Thread group\nPodemos los resultados en el elemento Summary Report de nuestro proyecto\n","date":"June 29, 2021","permalink":"/2021/jmeter-recording-controller/","section":"Posts","summary":"En el anterior post usabamos Selenium para grabar la funcionalidad de una pag;ína web y luego automatizar las pruebas. En esta ocasión vamos a usar JMeter Recording Controller para grabar las peticiones HTTP que se realizan a un sitio y con ello simular la navegación de un usuario.\nEste tipo de escenarios nos puede servir para realizar pruebas de carga o estrés a nuestro servidor de aplicaciones. Al ejecutar peticiones HTTP, sin necesidad de una UI (como chromiun o gecko), podemos enviar varios ciclos de peticiones al mismo tiempo, sin necesidad de ver afectada la memoria de nuestro cliente.","title":"Tests con JMeter Recording Controller"},{"content":"","date":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java"},{"content":"En esta oportunidad vamos a ver como realizar tests con Selenium y exportarlos a JUnit. Y luego leeremos el archivo JAR generado por JUnit para incluirlo en JMeter.\nPrimero necesitamos instalar algunas cosas:\nSelenium\nJMeter\nJMeter Plugin WebDriver\nWebDrivers de Chrome, Firefox y/o Edge\nWebDriver Language Bindings para Java\nIntellij Idea o algun otro IDE para Java\nCon todo esto podemos comenzar a grabar nuestras pruebas en Selenium. A continuación tenemos un ejemplo:\nLuego podemos exportar el script de Selenium a un archivo JUnit:\nFinalmente abrimos nuestro IDE de Java (aqui vamos a usar Intellij Idea) y creamos un proyecto de tipo JAVA para añadir nuestro archivo. También se debe crear una carpeta \u0026ldquo;lib\u0026rdquo; con las archivos WebDriver Language Bindings descargados desde la pagina de Selenium. La estructura del proyecto debe quedar mas o menos asi:\nAhora se deben agregar los paquetes (o librerias) necesarias(como el JUnit); pero en caso de las librerías de Selenium, se deben referenciar los JARs. Para ello, dentro de Intellij Idea vamos al menú:\nFile \u0026amp;rarr; Project Structure Seleccionamos la sección de Dependencies en:\nProject Settings \u0026amp;rarr; Project Structure \u0026amp;rarr; Dependencies Alli agregamos todos los Jars descargados, incluidos los que se encuentran dentro de la carpeta \u0026ldquo;libs\u0026rdquo;:\nEn algunos casos es necesario \u0026ldquo;ajustar\u0026rdquo; los selectores de los elementos. También podemos añadir instrucciones para que el test \u0026ldquo;espere\u0026rdquo; a la existencia de determinados elementos:\nWebDriverWait wait = new WebDriverWait(driver, 10); ... ... ... wait.until(ExpectedConditions.presenceOfElementLocated(By.linkText(\u0026#34;test.00325\u0026#34;))); wait.until(ExpectedConditions.elementToBeClickable(By.linkText(\u0026#34;test.00325\u0026#34;))); También se debe configurar el PATH de los WebDrivers; para ello modificamos la función public void setUp() para que quede asi:\n@Before public void setUp() { System.setProperty(\u0026#34;webdriver.chrome.driver\u0026#34;,\u0026#34;C:\\\\Program Files\\\\SeleniumWebDrivers\\\\chromedriver91.exe\u0026#34;); System.setProperty(\u0026#34;webdriver.gecko.driver\u0026#34;,\u0026#34;C:\\\\Program Files\\\\SeleniumWebDrivers\\\\geckodriver.exe\u0026#34;); //driver = new FirefoxDriver(); driver = new ChromeDriver(); js = (JavascriptExecutor) driver; vars = new HashMap\u0026lt;String, Object\u0026gt;(); } En esta sección se determina si se va a usar Chrome, Firefox o Edge para las pruebas\nPara terminar, en el caso de Intellij Idea, es necesario especificar la \u0026ldquo;configuración\u0026rdquo; de compilación. Para ello, se va a utilizar un proyecto de NUnit para que quede similar a:\nYa podemos ejecutar el proyecto y ver cómo nuestro test grabado en Selenium se reproduce integramente como una caso de pruebas de JUnit. Para eso se procede a crear el archivo JAR a partir del código generado. Para ello debemos crear \u0026ldquo;Artefacts\u0026rdquo; en Intellij Idea desde el siguiente menu:\nFile \u0026amp;rarr; Project Structure Seleccionamos la sección de Artifacts en:\nProject Settings \u0026amp;rarr; Artifacts \u0026amp;rarr; NEW Es importante marcar la opción Include in project builder y anotar el PATH de salida:\nCon todo esto, ya se tiene el archivo JAR necesario para importarlo a JMeter. Para ello, copiamos el JAR en el siguiente path:\n\\jmeter-x.x\\lib\\junit Ahora, abrimos JMeter y añadimos los siguientes elementos:\nAdd \u0026amp;rarr; Threads(Users) \u0026amp;rarr; Thread Group --Dentro del Thread Group añadir: Add \u0026amp;rarr; Sampler \u0026amp;rarr; JUnit Request Add \u0026amp;rarr; Listener \u0026amp;rarr; Summary Report Add \u0026amp;rarr; Listener \u0026amp;rarr; View Results Tree Add \u0026amp;rarr; Listener \u0026amp;rarr; Graph Results Add \u0026amp;rarr; Listener \u0026amp;rarr; Simple Data Writer Con esto, podemos ir al modulo JUnit Request y marcar la opción Search for JUnit 4 annotations (instead of JUnit3); esto desplegará las ClassName de los JARs que estan en la carpeta \\jmeter-x.x\\lib\\junit:\nPodemos marcar las opciones Append assertion errors y Append runtime exceptions que se encuentran en la parte inferior de la ventana de configuración del JUnit Request.\nCon todo esto, podemos ejecutar el TEST y veremos como se reproduce el script de nuestra librería JUnit, presionando el boton START de la barra de herramientas, o presionando los botones Ctrl + R\nBonus #Podemos crear nuestro proyecto como un WebDriver Sampler, por lo que debemos descargar el JMeter Plugin WebDriver y descomprimir el archivo en la carpeta lib de la instalación de JMeter.\nAhora, abrimos JMeter y añadimos los siguientes elementos:\nAdd \u0026amp;rarr; Config Element \u0026amp;rarr; jp@gc Chrome Driver Config Add \u0026amp;rarr; Threads(Users) \u0026amp;rarr; Thread Group --Dentro del Thread Group añadir: Add \u0026amp;rarr; Sampler \u0026amp;rarr; jp@gc WebDriver Sampler Add \u0026amp;rarr; Listener \u0026amp;rarr; Summary Report Add \u0026amp;rarr; Listener \u0026amp;rarr; View Results Tree Add \u0026amp;rarr; Listener \u0026amp;rarr; Graph Results Add \u0026amp;rarr; Listener \u0026amp;rarr; Simple Data Writer En el elemento jp@gc WebDriver Sampler podemos añadir nuestro propio código basado en lo que que se realizó con Selenium y JUnit:\nSe puede acceder a este enlace para ver la documentación sobre Web Driver Sampler.\nEl codigo es muy similar a lo que se tiene escrito en Java para JUnit\nvar pkg = JavaImporter(org.openqa.selenium, org.openqa.selenium.support.ui) WDS.sampleResult.sampleStart() try { WDS.browser.get(\u0026#39;https://testshop.incognito.ie/\u0026#39;) var wait = new pkg.WebDriverWait(WDS.browser, 10) //Login var lnkSigIn = WDS.browser.findElement(pkg.By.linkText(\u0026#34;Sign In\u0026#34;)); lnkSigIn.click(); var txtUser = WDS.browser.findElement(pkg.By.id(\u0026#34;email\u0026#34;)); txtUser.sendKeys(\u0026#34;mailTest20211@example.com\u0026#34;); var txtPass = WDS.browser.findElement(pkg.By.id(\u0026#34;pass\u0026#34;)); txtPass.sendKeys(\u0026#34;Test20211\u0026#34;); var btnLogin = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;.primary:nth-child(1) \u0026gt; #send2 \u0026gt; span\u0026#34;)); btnLogin.click(); //GoTo Category wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.id(\u0026#34;ui-id-3\u0026#34;))) var hrefCategoria = WDS.browser.findElement(pkg.By.id(\u0026#34;ui-id-3\u0026#34;)); hrefCategoria.click(); //Goto Page 5 wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.cssSelector(\u0026#34;#layer-product-list \u0026gt; div:nth-child(6) \u0026gt; div.pages \u0026gt; ul \u0026gt; li:nth-child(5) \u0026gt; a \u0026gt; span:nth-child(2)\u0026#34;))) wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;#layer-product-list \u0026gt; div:nth-child(6) \u0026gt; div.pages \u0026gt; ul \u0026gt; li:nth-child(5) \u0026gt; a \u0026gt; span:nth-child(2)\u0026#34;))) var hrefPage5 = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;#layer-product-list \u0026gt; div:nth-child(6) \u0026gt; div.pages \u0026gt; ul \u0026gt; li:nth-child(5) \u0026gt; a \u0026gt; span:nth-child(2)\u0026#34;)) hrefPage5.click() //Enter product details wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.linkText(\u0026#34;test.00325\u0026#34;))) wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.linkText(\u0026#34;test.00325\u0026#34;))) var hrefProduct = WDS.browser.findElement(pkg.By.linkText(\u0026#34;test.00325\u0026#34;)); hrefProduct.click(); //Add to cart wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.cssSelector(\u0026#34;#product-addtocart-button \u0026gt; span\u0026#34;))) wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;#product-addtocart-button \u0026gt; span\u0026#34;))) var btnAddToCart = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;#product-addtocart-button \u0026gt; span\u0026#34;)); btnAddToCart.click(); //GoTo Category wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.id(\u0026#34;ui-id-3\u0026#34;))) hrefCategoria = WDS.browser.findElement(pkg.By.id(\u0026#34;ui-id-3\u0026#34;)); hrefCategoria.click(); //Goto Page 5 wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.cssSelector(\u0026#34;#layer-product-list \u0026gt; div:nth-child(6) \u0026gt; div.pages \u0026gt; ul \u0026gt; li:nth-child(5) \u0026gt; a \u0026gt; span:nth-child(2)\u0026#34;))) wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;#layer-product-list \u0026gt; div:nth-child(6) \u0026gt; div.pages \u0026gt; ul \u0026gt; li:nth-child(5) \u0026gt; a \u0026gt; span:nth-child(2)\u0026#34;))) hrefPage5 = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;#layer-product-list \u0026gt; div:nth-child(6) \u0026gt; div.pages \u0026gt; ul \u0026gt; li:nth-child(5) \u0026gt; a \u0026gt; span:nth-child(2)\u0026#34;)) hrefPage5.click() //Delete cart wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.cssSelector(\u0026#34;.showcart\u0026#34;))); wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;.showcart\u0026#34;))); var menuCart = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;.showcart\u0026#34;)); menuCart.click(); wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.cssSelector(\u0026#34;.delete\u0026#34;))); wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;.delete\u0026#34;))); var cartDelete = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;.delete\u0026#34;)); cartDelete.click(); wait.until(pkg.ExpectedConditions.presenceOfElementLocated(pkg.By.cssSelector(\u0026#34;.action-primary \u0026gt; span\u0026#34;))); wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;.action-primary \u0026gt; span\u0026#34;))); var confirmDelete = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;.action-primary \u0026gt; span\u0026#34;)); confirmDelete.click(); WDS.browser.manage().window().setSize(new pkg.Dimension(1280, 1024)) //LogOut wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;body \u0026gt; div.page-wrapper \u0026gt; div.header-placeholder \u0026gt; div.page-header.page-header-v2 \u0026gt; header \u0026gt; div.header.content \u0026gt; div.header_right \u0026gt; ul \u0026gt; li.customer-welcome \u0026gt; span\u0026#34;))); var menuUser = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;body \u0026gt; div.page-wrapper \u0026gt; div.header-placeholder \u0026gt; div.page-header.page-header-v2 \u0026gt; header \u0026gt; div.header.content \u0026gt; div.header_right \u0026gt; ul \u0026gt; li.customer-welcome \u0026gt; span\u0026#34;)); menuUser.click(); wait.until(pkg.ExpectedConditions.elementToBeClickable(pkg.By.cssSelector(\u0026#34;.active .authorization-link \u0026gt; a\u0026#34;))); var menuLogOut = WDS.browser.findElement(pkg.By.cssSelector(\u0026#34;.active .authorization-link \u0026gt; a\u0026#34;)); menuLogOut.click(); } catch (err) { WDS.log.error(err.message) var screenshot = WDS.browser.getScreenshotAs(pkg.OutputType.FILE) screenshot.renameTo(new java.io.File(\u0026#39;screenshot.png\u0026#39;)) exception = err } WDS.sampleResult.sampleEnd() Finalmente en el apartado de Chrome Driver Config se debe establecer el PATH del WebDriver de Chrome:\nAl igual que con JUnit, los resultados de la ejecuci;ón se almacenan en los Listeners\n","date":"June 25, 2021","permalink":"/2021/selenium-junit-y-jmeter/","section":"Posts","summary":"En esta oportunidad vamos a ver como realizar tests con Selenium y exportarlos a JUnit. Y luego leeremos el archivo JAR generado por JUnit para incluirlo en JMeter.\nPrimero necesitamos instalar algunas cosas:\nSelenium\nJMeter\nJMeter Plugin WebDriver\nWebDrivers de Chrome, Firefox y/o Edge\nWebDriver Language Bindings para Java\nIntellij Idea o algun otro IDE para Java\nCon todo esto podemos comenzar a grabar nuestras pruebas en Selenium. A continuación tenemos un ejemplo:","title":"Selenium, JUnit y JMeter"},{"content":"","date":null,"permalink":"/tags/desarrollo/","section":"Tags","summary":"","title":"Desarrollo"},{"content":"Este post es un poco diferente a los que habitualmente se tienen aqui. Ahora vamos a realizar una guia rápida para preparar el desarrollo de una aplicación web MVC en NetCore con Conexion a PostgresSql via EntityFramework.\nRequisitos #Instalar o actualizar la herramientas de Entity Framework\ndotnet tool install --global dotnet-ef dotnet tool update --global dotnet-ef Instalar o actualizar la herramienta de scaffolding:\ndotnet tool install --global dotnet-aspnet-codegenerator dotnet tool update --global dotnet-aspnet-codegenerator Añadimos el certificado SSL en el PATH del proyecto\ndotnet dev-certs https --trust Inicio del proyecto #Añadir los paquetes Nuget:\nNpgsql\nMicrosoft.EntityFrameworkCore\nMicrosoft.EntityFrameworkCore.SqlServer.\nNpgsql.EntityFrameworkCore.PostgreSQL\nMicrosoft.VisualStudio.Web.CodeGeneration.Design\nMicrosoft.EntityFrameworkCore.Tools\nMicrosoft.AspNetCore.Identity.UI\nDespués de añadir las referencias del proyecto, se debe ejecutar un BUILD.\nAhora se puede crear los modelos con la herramienta dotnet ef, desde linea de comandos, desde el PATH de la solución:\ndotnet ef dbcontext scaffold \u0026#34;Host=localhost;Database=*NOMBRE_BD*;Username=*USUARIO_BD*;Password=*PASSWORD_BD*\u0026#34; Npgsql.EntityFrameworkCore.PostgreSQL -o Models -c ReAlDbContext --context-dir . --no-build --force --startup-project *NOMBRE_PROYECTO* En el archivo appsettings.json, se debe añadir el segmento \u0026ldquo;ConnectionStrings\u0026rdquo;:\n{ \u0026#34;ConnectionStrings\u0026#34;: { \u0026#34;DataAccessPostgreSqlProvider\u0026#34;: \u0026#34;User ID=*USUARIO_BD*;Password=*PASSWORD_BD*;Host=localhost;Port=5432;Database=*NOMBRE_BD*;Pooling=true;\u0026#34; }, [.....] } En el archivo Startup.cs, modificar el método ConfigureServices:\n// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // [ReAl] Usar una base de datos PostgreSQL var sqlConnectionString = Configuration.GetConnectionString(\u0026#34;DataAccessPostgreSqlProvider\u0026#34;); services.AddDbContext\u0026lt;ReAlDbContext\u0026gt;(options =\u0026gt; options.UseNpgsql( sqlConnectionString, b =\u0026gt; b.MigrationsAssembly(typeof(ReAlDbContext).Assembly.FullName) ) ); // [ReAl] Para acceder al archivo de configuracion services.AddSingleton\u0026lt;IConfiguration\u0026gt;(Configuration); services.AddControllersWithViews();\t} Ahora ya se puede crear las paginas para los ABMs (CRUD) con la creación automática (scaffolding) de los CONTROLLERS, realizados a partir de los MODELOS:\ndotnet aspnet-codegenerator controller --project . --force --controllerName *NOMBRE_TABLA_Controller* --model *NAMESPACE_MODELO*.*NOMBRE_MODELO* --dataContext ReAlDbContext --relativeFolderPath .\\Controllers\\ --useDefaultLayout --referenceScriptLibraries Tambien se puede crear solo vistas (Empty|Create|Edit|Delete|Details|List):\ndotnet aspnet-codegenerator view myDetails Details -outDir Views/*NOMBRE_TABLA* --project . --model *NAMESPACE_MODELO*.*NOMBRE_MODELO* --dataContext ReAlDbContext --useDefaultLayout --referenceScriptLibraries --force Y por ultimo, si se ve necesario reconstruir un modelo, se puede usar:\ndotnet ef dbcontext scaffold \u0026#34;Host=localhost;Database=*NOMBRE_BD*;Username=*USUARIO_BD*;Password=*PASSWORD_BD*\u0026#34; Npgsql.EntityFrameworkCore.PostgreSQL -o Models -c ReAlDbContext_new --context-dir . --no-build --force -t enc_informantes Personalizando el Scaffolding #Se puede crear los propios templates creando la carpeta Templates y añadiendo el siguiente fragmento al archivo csproj\n\u0026lt;ItemGroup\u0026gt; \u0026lt;Content Update=\u0026#34;appsettings.json\u0026#34;\u0026gt; \u0026lt;CopyToOutputDirectory\u0026gt;Always\u0026lt;/CopyToOutputDirectory\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;Content Remove=\u0026#34;Templates\\**\u0026#34;\u0026gt; \u0026lt;/Content\u0026gt; \u0026lt;/ItemGroup\u0026gt; Se puede copiar los templates originales desde:\nC:\\Users\\{USUARIO}\\.nuget\\packages\\microsoft.visualstudio.web.codegenerators.mvc\\3.1.1\\Templates Y a partir de alli, modificarlos según necesidad\n","date":"June 22, 2021","permalink":"/2021/gu%C3%ADa-rapida-para-scaffolding-de-aplicaciones-web-con-netcore-y-postgresql/","section":"Posts","summary":"Este post es un poco diferente a los que habitualmente se tienen aqui. Ahora vamos a realizar una guia rápida para preparar el desarrollo de una aplicación web MVC en NetCore con Conexion a PostgresSql via EntityFramework.\nRequisitos #Instalar o actualizar la herramientas de Entity Framework\ndotnet tool install --global dotnet-ef dotnet tool update --global dotnet-ef Instalar o actualizar la herramienta de scaffolding:\ndotnet tool install --global dotnet-aspnet-codegenerator dotnet tool update --global dotnet-aspnet-codegenerator Añadimos el certificado SSL en el PATH del proyecto","title":"Guía rapida para scaffolding de aplicaciones web con NetCore y PostgreSql"},{"content":"Quién soy? #Soy un desarrollador de aplicaciones que ha empezado con QBasic desde muy joven, y que luego he adquirido bastante experiencia en desarrollo con tecnologías .Net.\nConozco sobre Bases de Datos Ms SQLServer, PostgreSQL, ORACLE, SQLite y un poco de Firebird.\nActualmente estoy empezando a adentrarme en el mundo de PHP, jQuery, Angular y ramas afines.\nÉste es mi espacio donde pretendo compartir lo que voy aprendiendo.\nEscríbeme # 7.re.al.7@gmail.com\n","date":"January 1, 2021","permalink":"/about/","section":"Re-Al Dev World","summary":"Quién soy? #Soy un desarrollador de aplicaciones que ha empezado con QBasic desde muy joven, y que luego he adquirido bastante experiencia en desarrollo con tecnologías .Net.\nConozco sobre Bases de Datos Ms SQLServer, PostgreSQL, ORACLE, SQLite y un poco de Firebird.\nActualmente estoy empezando a adentrarme en el mundo de PHP, jQuery, Angular y ramas afines.\nÉste es mi espacio donde pretendo compartir lo que voy aprendiendo.\nEscríbeme # 7.","title":"Sobre mí"},{"content":"","date":null,"permalink":"/tags/base-de-datos/","section":"Tags","summary":"","title":"Base De Datos"},{"content":"","date":null,"permalink":"/tags/documentacion/","section":"Tags","summary":"","title":"Documentacion"},{"content":"Recientemente conoci la herramienta SchemaSpy para documentar Bases de Datos, generando documentación visual, detallada, pero sobretodo, muy entendible.\nEsta entrada servirá como introducción para ver lo que podemos hacer con SchemaSpy y su uso en nuestros proyectos. Lo primero, descargar e instalar SchemaSpy, para ellos seguimos los pasos descritos en su documentación oficial.\nSchemaSpy funciona con la mayoría de los motores de Base de Datos, como ser Oracle, MS-SQL, DB2, PostgreSQL, MySQL, etc; y se conecta a ellos a través de JDBC.\nLa forma más simple de ejecutar SchemaSpy es a través de linea de comandos, pasando como parámetros los datos para conexión a la Base de Datos que queremos documentar:\njava -jar schemaspy.jar -t mssql05 -dp C:/sqljdbc4-3.0.jar -db NOMBRE_BD -host SERVIDOR -port 1433 -s dbo -u USUARIO_BD -p PASSWORD_BD -o DIRECTORIO_DE_SALIDA Pero si queremos tener guardada la configuración utilizada para documentar la Base de Datos de uno de nuestros proyectos, es mejor utilizar un archivo de configuración normalmente denominado \u0026ldquo;schemaspy.properties\u0026rdquo;. A continuación un detalle de cómo se vé uno:\n# #java -jar .\\schemaspy-6.1.0.jar -configFile schemaspy.properties # # database type (pgsql, mssql, ora, mysql) schemaspy.t=pgsql # path to the database JDBC driver (postgresql-42.2.14.jar, mssql-jdbc-8.2.2.jre8.jar, ojdbc6.jar, mysql-connector-java-8.0.21.jar) schemaspy.dp=./drivers/postgresql-42.2.14.jar schemaspy.host=127.0.0.1 schemaspy.port=5432 # database name schemaspy.db=db_prueba # database user schemaspy.u=postgres schemaspy.p=********** # output folder for the generated resukt schemaspy.o=C:/mi_documentacion # database schema schemaspy.s=public Para utilizar este archivo como parámetro para SchemaSpy, debemos ejecutar\njava -jar schemaspy.jar -configFile schemaspy.properties SchemaSpy genera el resultado en archivos HTML, JS, CSS e imágenes (generadas con Graphviz), y todo es guardado en la carpeta de salida \u0026ldquo;schema.o\u0026rdquo;.\nLa documentación incluye todas las tablas, vistas, columnas, constraints, procedimientos almacenados, funciones y más detalles de cada tabla. Además se genera una representación visual del diagrama relacionales de las tablas en la Base de Datos:\nEsta herramienta es muy util para documentar nuestra Base de Datos o para verificar la estructura y problemas de Bases de Datos que nos encontramos en nuetsros distintos proyectos.\n","date":"July 18, 2020","permalink":"/2020/documentar-mi-base-de-datos-con-schemaspy/","section":"Posts","summary":"Recientemente conoci la herramienta SchemaSpy para documentar Bases de Datos, generando documentación visual, detallada, pero sobretodo, muy entendible.\nEsta entrada servirá como introducción para ver lo que podemos hacer con SchemaSpy y su uso en nuestros proyectos. Lo primero, descargar e instalar SchemaSpy, para ellos seguimos los pasos descritos en su documentación oficial.\nSchemaSpy funciona con la mayoría de los motores de Base de Datos, como ser Oracle, MS-SQL, DB2, PostgreSQL, MySQL, etc; y se conecta a ellos a través de JDBC.","title":"Documentar mi Base de Datos con SchemaSpy"},{"content":"Recientemente conoci la herramienta SchemaSpy para documentar Bases de Datos, generando documentación visual, detallada, pero sobretodo, muy entendible.\nEsta entrada servirá como introducción para ver lo que podemos hacer con SchemaSpy y su uso en nuestros proyectos. Lo primero, descargar e instalar SchemaSpy, para ellos seguimos los pasos descritos en su documentación oficial.\nSchemaSpy funciona con la mayoría de los motores de Base de Datos, como ser Oracle, MS-SQL, DB2, PostgreSQL, MySQL, etc; y se conecta a ellos a través de JDBC.\nLa forma más simple de ejecutar SchemaSpy es a través de linea de comandos, pasando como parámetros los datos para conexión a la Base de Datos que queremos documentar:\njava -jar schemaspy.jar -t mssql05 -dp C:/sqljdbc4-3.0.jar -db NOMBRE_BD -host SERVIDOR -port 1433 -s dbo -u USUARIO_BD -p PASSWORD_BD -o DIRECTORIO_DE_SALIDA Pero si queremos tener guardada la configuración utilizada para documentar la Base de Datos de uno de nuestros proyectos, es mejor utilizar un archivo de configuración normalmente denominado \u0026ldquo;schemaspy.properties\u0026rdquo;. A continuación un detalle de cómo se vé uno:\n# #java -jar .\\schemaspy-6.1.0.jar -configFile schemaspy.properties # # database type (pgsql, mssql, ora, mysql) schemaspy.t=pgsql # path to the database JDBC driver (postgresql-42.2.14.jar, mssql-jdbc-8.2.2.jre8.jar, ojdbc6.jar, mysql-connector-java-8.0.21.jar) schemaspy.dp=./drivers/postgresql-42.2.14.jar schemaspy.host=127.0.0.1 schemaspy.port=5432 # database name schemaspy.db=db_prueba # database user schemaspy.u=postgres schemaspy.p=********** # output folder for the generated resukt schemaspy.o=C:/mi_documentacion # database schema schemaspy.s=public Para utilizar este archivo como parámetro para SchemaSpy, debemos ejecutar\njava -jar schemaspy.jar -configFile schemaspy.properties SchemaSpy genera el resultado en archivos HTML, JS, CSS e imágenes (generadas con Graphviz), y todo es guardado en la carpeta de salida \u0026ldquo;schema.o\u0026rdquo;.\nLa documentación incluye todas las tablas, vistas, columnas, constraints, procedimientos almacenados, funciones y más detalles de cada tabla. Además se genera una representación visual del diagrama relacionales de las tablas en la Base de Datos:\nEsta herramienta es muy util para documentar nuestra Base de Datos o para verificar la estructura y problemas de Bases de Datos que nos encontramos en nuetsros distintos proyectos.\n","date":"July 18, 2020","permalink":"/2020/documentar-mi-base-de-datos-con-schemaspy/","section":"Posts","summary":"Recientemente conoci la herramienta SchemaSpy para documentar Bases de Datos, generando documentación visual, detallada, pero sobretodo, muy entendible.\nEsta entrada servirá como introducción para ver lo que podemos hacer con SchemaSpy y su uso en nuestros proyectos. Lo primero, descargar e instalar SchemaSpy, para ellos seguimos los pasos descritos en su documentación oficial.\nSchemaSpy funciona con la mayoría de los motores de Base de Datos, como ser Oracle, MS-SQL, DB2, PostgreSQL, MySQL, etc; y se conecta a ellos a través de JDBC.","title":"Documentar mi Base de Datos con SchemaSpy"},{"content":"Hoy vamos a ver como se puede configurar la integración de los Issues creados en JIRA para verlos como Tasks en los IDEs de Jetbrains (especificamente en Rider).\nPrimero se debe configurar los Servidores desde el IDE. Para ello ingresamos a Tools | Task \u0026amp; Contexts | Configure Servers\u0026hellip;.\nLuego se agrega el Servidor de tipo Jira:\nEn el cuadro de información general se debe colocar la siguiente información:\nServer URL: Es donde esta alojado nuestro servidor JIRA. Por ejemplo https://xxxxxxxx.atlassian.net\nEmail: Es la dirección de nuestro correo electrónico de usuario de JIRA\nAPI Token: Es el token de acceso provisto por Jira. Este Token se puede obtener desde https://id.atlassian.com/manage-profile/security/api-tokens.\nSearch: Es el filtro que se utilizará para mostrar los Issues en el pane de Tareas Tasks. El filtro recomendado es: project = \u0026ldquo;NOMBRE_PROYECTO\u0026rdquo; and assignee = currentUser() and status != Done order by updated\nPresionamos el botón Test para verificar que nuestra configuración es correcta:\nSe pueden\nCon ello, nuestra integración de Rider con Jira ya está terminada.\nSe pueden ver los issues creados desde el propio IDE, haciendo click en la opción Tools | Task \u0026amp; Contexts | Open Task\u0026hellip;:\nDespueés de realizar la respectiva sincronización se nos mostrará el detalle de Issues en JIRA (que no estén en estado FInalizado DONE):\nAl abrir un nuevo issue se nos mostrará la siguiente pantalla:\nEsta ventana nos permite configurar el issue abierto:\nUpdate issue state permite modificar el estado del issue. Este cambio se reflejará en JIRA\nCreate changelist creacrá un listado de cambios dentro del IDE, que podra ser centralizado via GIT al repositorio central\nShelve current changes si ésta opción está marcada, se dejará de lado los cambios actuales\nUse branch si se desea crear una rama en el repositorio.\nUna vez abierto el issue, podemos trabajar en los cambios, correcciones y mejoras relacionados.\nCuando se desee cerrar la tarea, se elige la opción Tools | Task \u0026amp; Contexts | Close active Task\u0026hellip;, donde se podrá definir el comportamiento del issue en JIRA:\nEn la ventana se puede elegir el unevo estado del issue, y además asociar un COMMIT.\nCon todo ello, podemos gestionar nuestro contenido de JIRAa desde el IDE; una ventaja muy util que nos da Jetbrains.\n","date":"April 13, 2020","permalink":"/2020/integrar-jira-con-los-ides-de-jetbrains/","section":"Posts","summary":"Hoy vamos a ver como se puede configurar la integración de los Issues creados en JIRA para verlos como Tasks en los IDEs de Jetbrains (especificamente en Rider).\nPrimero se debe configurar los Servidores desde el IDE. Para ello ingresamos a Tools | Task \u0026amp; Contexts | Configure Servers\u0026hellip;.\nLuego se agrega el Servidor de tipo Jira:\nEn el cuadro de información general se debe colocar la siguiente información:\nServer URL: Es donde esta alojado nuestro servidor JIRA.","title":"Integrar JIRA con los IDEs de Jetbrains"},{"content":"En éste articulo vamos a ver como se puede usar GULP para automatizar la reducción de archivos JS y CSS.\nGulp # Gulp es un conjunto de herramientas JavaScript open-source, contruido en Node.js y NPM, que ayuda a automatizar tareas comunes en el desarrollo de una aplicación, como pueden ser: mover archivos de una carpeta a otra, eliminarlos, minificar código, etc.\nMinimizar archivos usando Gulp # Se debe tener instalado Node.js, desde el sitio oficial. Ademas es necesario instalar las siguientes herramientas: npm i --global cssnano npm i --global del npm i --global gulp npm i --global gulp-concat npm i --global gulp-cssmin npm i --global gulp-gzip npm i --global gulp-header npm i --global gulp-htmlmin npm i --global gulp-postcss npm i --global gulp-rename npm i --global gulp-uglify npm i --global gulp-util npm i --global gulp-zip npm i --global merge-stream npm i --global run-sequence Se debe identificar cuáles y dónde están los archivos JS y CSS a minimizar. Esta información la ponemos en un archivo llamado \u0026lsquo;bundleconfig.json\u0026rsquo;: [ { \u0026#34;outputFileName\u0026#34;: \u0026#34;wwwroot/css/integrate.min.css\u0026#34;, \u0026#34;inputFiles\u0026#34;: [ \u0026#34;wwwroot/lib/bootstrap/dist/css/bootstrap.min.css\u0026#34;, \u0026#34;wwwroot/lib/font-awesome/css/font-awesome.min.css\u0026#34;, \u0026#34;wwwroot/lib/bootstrap-datepicker/dist/css/bootstrap-datepicker3.min.css\u0026#34;, \u0026#34;wwwroot/lib/bootstrap-select/dist/css/bootstrap-select.min.css\u0026#34;, \u0026#34;wwwroot/css/styles.css\u0026#34; ] }, { \u0026#34;outputFileName\u0026#34;: \u0026#34;wwwroot/js/integrate.min.js\u0026#34;, \u0026#34;inputFiles\u0026#34;: [ \u0026#34;wwwroot/lib/jquery/dist/jquery.min.js\u0026#34;, \u0026#34;wwwroot/lib/bootstrap/dist/js/bootstrap.min.js\u0026#34;, \u0026#34;wwwroot/lib/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js\u0026#34;, \u0026#34;wwwroot/lib/bootstrap-select/dist/js/bootstrap-select.min.js\u0026#34;, \u0026#34;wwwroot/js/custom.js\u0026#34; ], \u0026#34;minify\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;renameLocals\u0026#34;: true }, \u0026#34;sourceMap\u0026#34;: false } ] Se debe crear el archivo \u0026lsquo;gulpfile.js\u0026rsquo; y añadir el código requerido en la cabecera: \u0026#34;use strict\u0026#34;; var gulp = require(\u0026#34;gulp\u0026#34;), concat = require(\u0026#34;gulp-concat\u0026#34;), cssmin = require(\u0026#34;gulp-cssmin\u0026#34;), htmlmin = require(\u0026#34;gulp-htmlmin\u0026#34;), uglify = require(\u0026#34;gulp-uglify\u0026#34;), merge = require(\u0026#34;merge-stream\u0026#34;), del = require(\u0026#34;del\u0026#34;), gzip = require(\u0026#39;gulp-gzip\u0026#39;), bundleconfig = require(\u0026#34;./bundleconfig.json\u0026#34;); var regex = { css: /\\.css$/, html: /\\.(html|htm)$/, js: /\\.js$/ }; Se procede a definir las posibles tareas. En nuestro ejemplo vamos a definir 8 tasks: gulp.task(\u0026#39;gzip_js\u0026#39;, () =\u0026gt; { return gulp.src(\u0026#39;wwwroot/js/ziran.min.js\u0026#39;) .pipe(gzip()) .pipe(gulp.dest(\u0026#39;wwwroot/js/\u0026#39;)); }); gulp.task(\u0026#39;gzip_css\u0026#39;, () =\u0026gt; { return gulp.src(\u0026#39;wwwroot/css/ziran.min.css\u0026#39;) .pipe(gzip()) .pipe(gulp.dest(\u0026#39;wwwroot/css/\u0026#39;)); }); gulp.task(\u0026#39;min_js\u0026#39;, () =\u0026gt; { var tasks = getBundles(regex.js).map(function (bundle) { return gulp.src(bundle.inputFiles, { base: \u0026#34;.\u0026#34; }) .pipe(concat(bundle.outputFileName)) .pipe(uglify()) .pipe(gulp.dest(\u0026#34;.\u0026#34;)); }); return merge(tasks); }); gulp.task(\u0026#39;min_css\u0026#39;, () =\u0026gt; { var tasks = getBundles(regex.css).map(function (bundle) { return gulp.src(bundle.inputFiles, { base: \u0026#34;.\u0026#34; }) .pipe(concat(bundle.outputFileName)) .pipe(cssmin()) .pipe(gulp.dest(\u0026#34;.\u0026#34;)); }); return merge(tasks); }); gulp.task(\u0026#39;min_html\u0026#39;, () =\u0026gt; { var tasks = getBundles(regex.html).map(function (bundle) { return gulp.src(bundle.inputFiles, { base: \u0026#34;.\u0026#34; }) .pipe(concat(bundle.outputFileName)) .pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true })) .pipe(gulp.dest(\u0026#34;.\u0026#34;)); }); return merge(tasks); }); gulp.task(\u0026#39;clean\u0026#39;, () =\u0026gt; { var files = bundleconfig.map(function (bundle) { return bundle.outputFileName; }); return del(files); }); gulp.task(\u0026#39;watch\u0026#39;, () =\u0026gt; { getBundles(regex.js).forEach(function (bundle) { gulp.watch(bundle.inputFiles, [\u0026#34;min:js\u0026#34;]); }); getBundles(regex.css).forEach(function (bundle) { gulp.watch(bundle.inputFiles, [\u0026#34;min:css\u0026#34;]); }); getBundles(regex.html).forEach(function (bundle) { gulp.watch(bundle.inputFiles, [\u0026#34;min:html\u0026#34;]); }); }); gulp.task(\u0026#39;default\u0026#39;, gulp.series([\u0026#39;min_js\u0026#39;, \u0026#39;min_css\u0026#39;, \u0026#39;gzip_js\u0026#39;, \u0026#39;gzip_css\u0026#39;])); Finalmente tenemos una función auxiliar:\nfunction getBundles(regexPattern) { return bundleconfig.filter(function (bundle) { return regexPattern.test(bundle.outputFileName); }); } Para ejecutar la tarea \u0026lsquo;default\u0026rsquo;, se debe ejecutar desde linea de comandos:\ngulp ","date":"March 31, 2020","permalink":"/2020/comprimir-css-y-js-con-gulp/","section":"Posts","summary":"En éste articulo vamos a ver como se puede usar GULP para automatizar la reducción de archivos JS y CSS.\nGulp # Gulp es un conjunto de herramientas JavaScript open-source, contruido en Node.js y NPM, que ayuda a automatizar tareas comunes en el desarrollo de una aplicación, como pueden ser: mover archivos de una carpeta a otra, eliminarlos, minificar código, etc.\nMinimizar archivos usando Gulp # Se debe tener instalado Node.","title":"Comprimir CSS y JS con GULP"},{"content":"Hoy vamos a ver como actualizar las dependencias de un proyecto\nEsto quiere decir que los archivos \u0026ldquo;package.json\u0026rdquo; y \u0026ldquo;bower.json\u0026rdquo; van a cambiar, por lo que si deseas, puedes sacar un backup de los mismos.\nEn el caso de \u0026ldquo;package.json\u0026rdquo;, es necesario tener el paquete NPM Check Updates instalado:\nnpm i -g npm-check-updates Luego se ejecuta las siguientes instrucciones:\nnpm-check-updates -u npm install Para el caso de \u0026ldquo;bower.json\u0026rdquo; necesitamos tener instalado el paquete Bower Check Updates.\nnpm install -g bower-check-updates Luego ejecutamos las instrucciones para actualizar:\nbower-check-updates -u bower install Con ello, nuestras dependencias estaran actualizadas a su ultima version.\nEspero les sirva\n","date":"January 5, 2018","permalink":"/2018/actualizar-dependencias-npm-y-bower/","section":"Posts","summary":"Hoy vamos a ver como actualizar las dependencias de un proyecto\nEsto quiere decir que los archivos \u0026ldquo;package.json\u0026rdquo; y \u0026ldquo;bower.json\u0026rdquo; van a cambiar, por lo que si deseas, puedes sacar un backup de los mismos.\nEn el caso de \u0026ldquo;package.json\u0026rdquo;, es necesario tener el paquete NPM Check Updates instalado:\nnpm i -g npm-check-updates Luego se ejecuta las siguientes instrucciones:\nnpm-check-updates -u npm install Para el caso de \u0026ldquo;bower.json\u0026rdquo; necesitamos tener instalado el paquete Bower Check Updates.","title":"Actualizar dependencias NPM y BOWER"},{"content":"Si ya tenemos nuestra solucion de VisualStudio en Jenkins, el siguiente paso es configurar los reportes de CObertura y Metricas\nPara ello necesitamos las siguientes herramientas:\nOpenCover\nReportGenerator\nOpenToCoberturaConverter. Una vez descargado el paquete Nuget, procedemos a cambiar la extension del archivo de nupkg as zip. Al descomprimir el archivo zip, podremos encontrar un ejecutable en la carpeta Tools. Ese es el ejecutable que necesitamos.\nMetics Power Tools. EN mi caso, la version para VisualStudio 2015.\nNota: Todas las herramientas deberas ser descompresas en un directorio (digamos \u0026ldquo;C:\\JenkinsTools\u0026quot;), cada una con su respectiva carpeta\nTambien vamos a necesitar algunos plugins de Jenkins:\nHTML Publisher\nVisualStudio Code Metrics\nCobertura\nAhora procedemos a configurar el Plugin de CisuaStudio Code Metrics a través del menú: Administrar Jenkins -\u0026gt; Global Tool Configuration Buscamos el bloque perteneciente al plugin y lo configuramos tal cual se muestra en la captura:\nYa tenemos listo los plugins y las herramientas necesarias.\n***NOTA: En mi caso, como no tengo instalado VisualStudio en la maquina de Deployment, instale VisualStudio Code Metrics pero tambien tube que COPIAR todo el contenido de la carpeta de destino \u0026ldquo;C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Team Tools\\Static Analysis Tools\\FxCop\u0026rdquo; de mi maquina de desarrollo al servidor de Deployment ***\nConfigurando el Proyecto y los reportes #Ahora nos dirigimos a nuestro proyecto de Jenkins y accedemos al panel de Configuración, para añadir los siguientes pasos:\nAgregar un paso Ejecutar un comando de Windows con la siguiente instrucción:\n\u0026#34;C:\\JenkinsTools\\opencover\\OpenCover.Console.exe\u0026#34; -target:\u0026#34;C:\\JenkinsTools\\nunit\\nunit3-console.exe\u0026#34; -targetargs:\u0026#34;%JOB_NAME%.Tests\\bin\\Debug\\%JOB_NAME%.Tests.dll /framework:net-4.5 /xml:%JOB_NAME%NunitTestResults.xml /nologo /noshadow\u0026#34; -filter:\u0026#34;+[*]* -[%JOB_NAME%.Tests]*\u0026#34; -register:Path64 -hideskipped:Filter -output:%JOB_NAME%Coverage.xml Agregar otro paso Ejecutar un comando de Windows con la siguiente instrucción:\n\u0026#34;C:\\JenkinsTools\\ReportGenerator\\ReportGenerator.exe\u0026#34; -reports:%JOB_NAME%Coverage.xml -targetDir:CodeCoverageHTML Agregar un paso mas de tipo Ejecutar un comando de Windows con la siguiente instrucción:\n\u0026#34;C:\\JenkinsTools\\opencover_to_cobertura_converter\\OpenCoverToCoberturaConverter.exe\u0026#34; -input:%JOB_NAME%Coverage.xml -output:%JOB_NAME%Cobertura.xml -sources:%WORKSPACE% Finalmente agregamos un paso de Tipo VS Code Metrics PowerTool exec. Alli elegimos la herramienta previamente configurada, especificamos la(s) DLL(s) que deseamos se realice el analisis y un nombre para el archivo XML de salida:\nPublicación de los reportes #Ahora añadimos pasos al bloque que se ejecuta DESPUES del bloque Build:\nPrimero añadimos un paso de Tipo Publicar Informes de \u0026ldquo;Cobertura\u0026rdquo; y especificamos el nombre del archivo XML de origen. Aqui es muy importante respectar las Mayusculas y minusculas, porque si no, Jenkins no encuentra el archivo.\n{NOMBRE_DEL_PROYECTO_JENKINS}Cobertura.xml Ahora añadimos la generación de un reporte HTML basado en el resultado de OpenCover, con los siguientes parametros:\nCodeCoverageHTML # HTML directorio index.htm # Pagina de inicio del Reporte Code Coverage # Titulo del Reporte Y finalmente agregamos el reporte de Metricas de Visual Studio:\nEl nombre metrics.xml debe coincidir con el nombre utilizado como salida del paso VS Code Metrics PowerTool exec previamente configurado\n","date":"June 23, 2017","permalink":"/2017/cobertura-metricas-en-jenkins/","section":"Posts","summary":"Si ya tenemos nuestra solucion de VisualStudio en Jenkins, el siguiente paso es configurar los reportes de CObertura y Metricas\nPara ello necesitamos las siguientes herramientas:\nOpenCover\nReportGenerator\nOpenToCoberturaConverter. Una vez descargado el paquete Nuget, procedemos a cambiar la extension del archivo de nupkg as zip. Al descomprimir el archivo zip, podremos encontrar un ejecutable en la carpeta Tools. Ese es el ejecutable que necesitamos.\nMetics Power Tools. EN mi caso, la version para VisualStudio 2015.","title":"Cobertura y Metricas en Jenkins para proyectos .Net"},{"content":"","date":null,"permalink":"/tags/integracion-continua/","section":"Tags","summary":"","title":"Integracion Continua"},{"content":"Hola! El dia de ayer, 16 de Agosto, se liberó oficialmente la versión 2.0 de NetCore. Asi que por fin me animé a probarla.\nYa llevo avanzadas varias cosas en este nuevo mundo, pero ahora hablaré sobre cómo debería realizarse la gestión de paquetes con los gestores NPM, Bower y Gulp.\nNPM - Node Package Manager #Si bien uno de los propósitos de NPM es el desarrollo de aplicaciones Node, no sólo se puede usar para ese fin. Tambien puede utilizarse para gestion de paquetes \u0026ldquo;Node\u0026rdquo; (como angular-cli y demas). En una aplicacion ASP.Net Core se puede utilizar Node para obtener dependencias del lado del cliente, como por ejemplo Angular, JQuery o Bootstrap; pero tambien se puede obtener los beneficios de herramientas que automatizan el trabajo como Grunt o Gulp. Para tener una estructura mas \u0026ldquo;ordenada\u0026rdquo;, vamos a utilizar NPM para instalar herramientas del lado del servidor.\nUna vez tengamos nuestro Proyecto NetCore se puede agregar un archivo de Configuración NPM al proyecto:\nCuando el archivo este en el proyecto, se puede ver la configuración basica que contiene:\nSe puede agregar paquetes NPM al proyecto de dos formas:\nAgregando los nombres y versiones de los paquetes manualmente al archivo _package.json\nInstalando los paquetes NPM desde consola, pero con la opcion -S (Esta opción instalará el paquete, pero pondrá una linea en el archivo package.json con el nombre y la versión instalada). Por ejemplo:\nnpm install gulp -S Una vez se tengan identificados los paquetes a instalar, vemos que ocurre la magia:\nComo se puede ver, cada paquete identificado en el archivo package.json ha sido instalado y configurado como dependencia en el proyecto.\nNota: Como podrán ver, sólo se ha instalado GULP con NPM. Las librerias del lado del cliente van a instalarse con Bower. Esto con el propósito de tener un orden y clasificación referente a lo que se va autilizar en el lado del cliente y lo que se va a utilizar en el lado del servidor.\nBower #Vimos que instalar paquetes con NPM es muy sencillo. ¿Cierto?. Ahora vamos a instalar dependencias del lado del cliente como Bootstrap, JQuery y demas. Para ello, primero se debe agregar un archivo de configuración Bower en el Proyecto:\nCuando el archivo este en el proyecto, se puede ver la configuración basica que contiene:\nAl igual que NPM, se puede agregar paquetes desde BOWER al proyecto de dos formas:\nAgregando los nombres y versiones de los paquetes manualmente al archivo bower.json\nInstalando los paquetes BOWER desde consola, pero con la opcion \u0026ndash;save (Esta opción instalará el paquete, pero pondrá una linea en el archivo bower.json con el nombre y la versión instalada). Por ejemplo:\nbower install jquery --save Una vez se tengan identificados los paquetes a instalar, vemos que con Bower tambien ocurre la magia:\nComo se puede ver, cada paquete identificado en el archivo bower.json ha sido instalado y configurado como dependencia en el proyecto.\nPor defecto, Bower instala todo en la carpeta wwwroot/lib, aunque se puede especificar una nueva ruta en el archivo bower.json con:\n{ \u0026#34;directory\u0026#34; : \u0026#34;wwwroot/lib\u0026#34; } Gulp y Bundles #Hasta ahora ya tenemos instalado Gulp (con NPM) y todas las librerias necesarias del lado del cliente (con Bower). Normalmente lo que sigue es referenciar las librerias del lado del cliente en el _\u0026quot;Layout\u0026quot; principal del proyecto, pero esto no es aconsejable debido a que no es lo más optimo. Aqui es donde entra GULP para crear hacer el \u0026ldquo;Bundling\u0026rdquo; y minimizar las referencias (archivos CSS y JS) al maximo.\n\u0026ldquo;Bundling\u0026rdquo;: Es un termino que significa combinar multiples archivos en uno solo. La idea tras de esto es minimizar la cantidad de peticiones al servidor, con el fin de mejorar el rendimiento del primer cargado de la pagina.\nMinimización: Puede aplicarse a diferentes metodos de optimización de código para reducir el tamaño de los archivos (CSS, JS e incluso imagenes). Una práctica comun de \u0026ldquo;minimización\u0026rdquo; es acortar los nombres de las variables a una unica letra.\nPara aplicar \u0026ldquo;Bundling\u0026rdquo; y minimización en el proyecto, es necesario el archivo bundleconfig.json que por lo general viene por defecto en los proyectos NetCore de tipo ASP:Net. Un ejemplo de este archivo es:\nComo se puede ver, es un archivo JSON que contiene una configuración básica de cómo realizar el Bundling y la minimización:\n[ { \u0026#34;outputFileName\u0026#34;: \u0026#34;wwwroot/css/integrate.min.css\u0026#34;, \u0026#34;inputFiles\u0026#34;: [ \u0026#34;wwwroot/css/sb-admin-2.css\u0026#34;, \u0026#34;wwwroot/lib/metisMenu/dist/metisMenu.css\u0026#34;, \u0026#34;wwwroot/lib/bootstrap-table/dist/bootstrap-table.css\u0026#34; ], \u0026#34;minify\u0026#34;: { \u0026#34;enabled\u0026#34;: true } }, { \u0026#34;outputFileName\u0026#34;: \u0026#34;wwwroot/js/integrate.min.js\u0026#34;, \u0026#34;inputFiles\u0026#34;: [ \u0026#34;wwwroot/js/sb-admin-2.js\u0026#34;, \u0026#34;wwwroot/lib/bootstrap-table/dist/bootstrap-table.js\u0026#34; ], \u0026#34;minify\u0026#34;: { \u0026#34;enabled\u0026#34;: true, \u0026#34;renameLocals\u0026#34;: true }, \u0026#34;sourceMap\u0026#34;: false } ] Se tiene dos bloques: el primero para archivos CSS y el segundo para archivos JS. Se puede ver que ambos configuran un conjunto de archivos en entrada (inputFiles), y el archivo de salida que se generará (outputFileName).\nAhora, VisualStudio nos ofrece la opción de crear nuestro archivo GULP a partir del archivo bundleconfig.json. Para ello, se hace click derecho sobre el archivo y se selecciona la opción \u0026ldquo;Bundler \u0026amp; Minifier\u0026rdquo; y luego \u0026ldquo;Convert to Gulp\u0026rdquo;:\nEsto no sólo creará el archivo gulpfile.js, si no que tambien instalará los paquetes necesarios del lado del servidor (con NPM), y los adicionará automaticamente al archivo packages.json.\nPara realizar un ensayo de lo que hará nuestro proceso de \u0026ldquo;bundling\u0026rdquo; y minificación, hacemos click derecho sobre gulpfile.js y seleccionamos la opción \u0026ldquo;Explorador del ejecutador de Tareas\u0026rdquo;, para ver el siguiente panel:\nDentro de esta ventana, en la parte izquierda se puede ver un listado de \u0026ldquo;Tareas\u0026rdquo;. Cada una de estas tereas realiza un trabajo distinto y se puede ir probando de una en una. Sin embrago la más interesante es la que indica \u0026ldquo;min\u0026rdquo;: Esta tarea realiza el \u0026ldquo;bundling\u0026rdquo; y minimización de todos los archivos especificados en bundleconfig.json para generar los archivos compresos. Para ejecutar la tarea, se hace click derecho sobre la misma y se selecciona \u0026ldquo;Ejecutar\u0026rdquo;:\nSi todo salió bien, se verifica la ruta donde se supone tienen que estar los archivos de salida:\nLo unico que resta es referencia este archivo simplificado en nuestras vistas.\n","date":"June 23, 2017","permalink":"/2017/npm-bower-y-gulp-en-asp-netcore/","section":"Posts","summary":"Hola! El dia de ayer, 16 de Agosto, se liberó oficialmente la versión 2.0 de NetCore. Asi que por fin me animé a probarla.\nYa llevo avanzadas varias cosas en este nuevo mundo, pero ahora hablaré sobre cómo debería realizarse la gestión de paquetes con los gestores NPM, Bower y Gulp.\nNPM - Node Package Manager #Si bien uno de los propósitos de NPM es el desarrollo de aplicaciones Node, no sólo se puede usar para ese fin.","title":"Npm, Bower y Gulp en ASP NetCore"},{"content":"Ultimamente he estado viendo varias herramientas para ver las métricas y estadísticas del codigo fuente de algun proyecto; pero de entre todas estas herramientas destaca SonarQube, por una sencilla razon: es open source.\nSonarQube es una plataforma de código abierto para el análisis de la calidad de nuestro código y para obtener métricas que pueden ayudar a mejorar la calidad del código de cualquiera de nuestros programas.\nEs una herramienta que nos sirve para auditar el código dentro de nuestro ciclo de desarrollo de nuestra aplicación. Existen distintos tipos de problemas que se pueden encontrar en un código, éstos pueden clasificarse en 5 grupos según su severidad: Bloqueador, crítico, grave, menor e informativo.\nInstalando y configurando SonarQube #Primero que necesitamos descargar algunas cosas desde SonarQube:\nEl servidor desde su respectiva página.\nEl Scanner para el MsBuild desde aqui.\nEl SonarQubeScanner desde aqui.\nUna vez se descargue, descomprimimos todo. Yo tengo esta estructura:\nDespues agregamos dos variables de entorno a nuestro PATH:\nLa ruta al directorio sonar-scanner-msbuild\nLa ruta al directorio bin de sonar-scanner\nEn mi caso, sería:\nAhora podemos configurar la conexión a la BD. Yo voy a utilizar un servidor PostgreSql (SonarQube también soporta otros tipos de Bases de Datos), por lo que tengo que encontrar el siguiente archivo:\n/PATH_SONARQUBE_SERVER/conf/sonar.properties Editando ese archivo, colocamos el nombre de usuario y la contraseña de conexión al servidor:\n# User credentials. # Permissions to create tables, indices and triggers must be granted to JDBC user. # The schema must be created first. sonar.jdbc.username=USUARIO_BD sonar.jdbc.password=PASSWORD_BD Nuevamente, como yo voy a utilizar PostgreSql, descomento la sección correspondiente e introduzco la URL JDBC de conexion:\n#----- PostgreSQL 8.x/9.x # If you don\u0026#39;t use the schema named \u0026#34;public\u0026#34;, please refer to http://jira.sonarsource.com/browse/SONAR-5000 sonar.jdbc.url=jdbc:postgresql://IP_SERVIDOR/sonarqube Esto quiere decir que debo crear una nueva Base de Datos en mi servidor con el nombre sonarqube\nPor ultimo, se debe configurar los Scanners descargados. Para ello se busca el archivo sonar-scanner.properties en cada uno de los scaners (normalmente se encuentran en la carpeta conf) y se especifica el servidor SonarQube donde enviarán todas las estadísticas. Por defecto, SonarQube escucha en el puerto 9000 (aunque esto se puede cambiar):\n#----- Default SonarQube server sonar.host.url=http://localhost:9000 Este cambio debe realizarse en todos los Scanners que tengamos descargados\nAhora si, podemos iniciar el Servidor SonarQube. Para ello nos vamos a la carpeta bin del directorio donde está el SONARQUBE. Veremos varias carpetas:\nDependiendo del Sistema OPerativo que tengamos, se accede a su respectiva carpeta y se inicia el servicio con el archivo StartSonar.bat.\nSi quisiéramos instalar SonarQube como servicio, podemos ejecutar InstallNTService.bat, y para desinstalarlo: UninstallNTService.bat\nAccedemos a la direccion http://localhost:9000/ y podremos ver nuestro servidor SonarQube ejecutándose:\nEl usuario y contraseña por defecto son admin/admin\nCreando el Proyecto en SonarQube y ejecutando el análisis con MsBuild #Antes de ejecutar el scanner sobre nuestro proyecto, es necesario crearlo. Para ello ingresamos a nuestro servidor de SonarQube (http://localhost:9000/), y nos vamos a \u0026ldquo;Management Projects\u0026rdquo;:\nAhi podemos crear un proyecto especificando el nombre y una palabra clave para identificarlo:\nEste nombre y palabra clave será utilizados al momento de realizar el escaneo con sonar-scanner-msbuild o con cualquier otro escanner.\nAhora nos dirigimos al directorio raiz de la solución de VisualStudio que deseamos analizar y ejecutamos las siguientes instrucciones:\nMSBuild.SonarQube.Runner begin /key:\u0026#34;SANEAMIENTO\u0026#34; /name:\u0026#34;Segip.Licencias.Saneamiento\u0026#34; /version:1 MsBuild MSBuild.SonarQube.Runner end Para que estos comandos funcionen, la carpeta de sonar-scanner-msbuild debe estar en la variable de entorno PATH.\nA mi me pasó que ejecutando el comando MsBuild, se ejecutó la versión del .NetFramework 4, y no la del VisualStudio 14.0 Esto hacia que la instruccion me diera errores, por lo que para ejecutar el MSBuild le di todo el PATH:\nC:\\Program Files (x86)\\MSBuild\\14.0\\Bin\\MSBuild.exe Una vez termine el analisis (el tiempo variará dependiendo del tamño del proyecto), podemos acceder al servidor SonarQube y verificar el procesamiento del escaneo:\nCuando la tarea en Background termine, podremos ver las estadisticas del proyecto\n","date":"June 22, 2017","permalink":"/2017/sonarqube-para-estadisticas-en-visualstudio/","section":"Posts","summary":"Ultimamente he estado viendo varias herramientas para ver las métricas y estadísticas del codigo fuente de algun proyecto; pero de entre todas estas herramientas destaca SonarQube, por una sencilla razon: es open source.\nSonarQube es una plataforma de código abierto para el análisis de la calidad de nuestro código y para obtener métricas que pueden ayudar a mejorar la calidad del código de cualquiera de nuestros programas.\nEs una herramienta que nos sirve para auditar el código dentro de nuestro ciclo de desarrollo de nuestra aplicación.","title":"SonarQube para metricas del Codigo en VisualStudio"},{"content":"Estos dias me he planteado realizar la implementacion de CI (Integración Continua) en mis proyectos. Para ello he acudido a Jenkins.\nJenkins # Jenkins nos permite, de una manera facil e intuitiva, programar el despliegue de nuestras aplicaciones. Verán que instalarlo es muy simple. Aquí las instrucciones para instalarlo en cualquier S.O. En mi caso, voy a usarlo sobre Windows Server.\nPor defecto, el puerto por el que escucha Jenkins es el 8080, pero podemos modificarlo para escuchar cualquier otro puerto. Esta modificación se realiza en el archivo \u0026ldquo;jenkins.xml\u0026rdquo; que se encuentra en el directorio raiz de la instalación de Jenkins\nEn mi caso, utilizare el puerto 82. La configuración debera quedar asi:\n\u0026lt;arguments\u0026gt;-Xrs -Xmx256m -Dhudson.lifecycle=hudson.lifecycle.WindowsServiceLifecycle -jar \u0026#34;%BASE%\\jenkins.war\u0026#34; --httpPort=82 --webroot=\u0026#34;%BASE%\\war\u0026#34;\u0026lt;/arguments\u0026gt; Para que la configuración tenga efecto tuve que reiniciar el servicio a través del \u0026ldquo;Administrador de Servicios\u0026rdquo; de Windows.\nUna vez se tenga todo listo, debe procederse a instalar los plugins recomendados por Jenkins, y después debe instalarse los siguientes plugins a través de \u0026ldquo;Administrar Jenkins\u0026rdquo;-\u0026gt;\u0026ldquo;Administrar Plugins\u0026rdquo;:\nBitbucket Build Status Notifier Plugin Bitbucket Plugin MSBuild Plugin MSTest plugin Ahora procedemos a configurar nuestro MSBuild. Esta herramienta nos permitirá compilar proyectos .Net.\nConfigurar MSBuild y Git #Para realizar estas configuraciones, primero debemos tener instaladas estas herramientas en el servidor de despliegue (aka. donde está Jenkins):\nMsBuild Git Una vez tengamos instalados estos utilitarios, ingresamos a \u0026ldquo;Administrar Jenkins\u0026rdquo;-\u0026gt;\u0026ldquo;Global Tool Configuration\u0026rdquo; para ajustar el PATH de cada ejecutable:\nPodemos crear varias instancias de MsBuild, de acuerdo a la version del mismo. En mi caso estoy utilizando la versión 14, correspondiente a VisualStudio 2015.\nCrear Tareas #Una vez tengamos todo configurado, podemos crear nuestra primera tarea, haciendo click en la barra lateral izquierda.\nPonemos un nombre a la tarea (yo prefiero poner el nombre de mi Solución .Net) y elegimos la opcion \u0026ldquo;Crear un proyecto estilo libre\u0026rdquo; y presionamos el boton \u0026ldquo;OK\u0026rdquo;:\nAhora realizamos la configuración del Proyecto.\nEn mi caso voy a elegir, como origen del codigo fuente, un repositorio git en BitBucket:\nAl configurar un repositorio en BitBucket, me pedirá que cree mis credenciales de acceso Git.\nTambien puedo configurar (si tengo el plugin de BitBucket), que se ejecute una \u0026ldquo;Build\u0026rdquo; cada vez que se haga un PUSH al repositorio.\nSi marcamos esta opcioón, debemos configurar un WebHook en el repositorio de BitBucket apuntando hacia nuestro servidor de despliegue:\nLa URL del WeebHook en BitBucket debe ser:\nhttp://USUARIO_JENKINS:PASSSWORD_JENKINS@SERVIDOR_JENKINS:PUERTO_JENKINS/bitbucket-hook/ Ahora, volviendo a la configuración de la Tarea en Jenkins, pasamos a la parte mas importante:\nEn el bloque de Ejecutar podemos añadir varias acciones:\n1. Restaurar paquetes Nuget de la Solucion #Antes de empezar a realizar el despliegue de nuestros proyectos, es necesario restaurar los paquetes Nuget utilizados en la solución. Si bien existen plugins para Jenkins que nos ayudan con esto, es mejor realizar la tarea a través de un paso de tipo \u0026ldquo;Ejecutar un comando de Windows\u0026rdquo;\nObviamente, esto implica que tenemos que tener instalado el ejecutable \u0026ldquo;Nuget.exe\u0026rdquo; en nuestro servidor de despliegue.\n\u0026#34;C:\\Program Files\\Nuget\\\u0026#34;nuget.exe restore ARCHIVO_DE_SOLUCION.sln 2. Ejecutar el MsBuild de Toda la solución #Para compilar toda la solución, agregamos un nuevo paso y elegimos la opcion \u0026ldquo;Build a VisualStudio project or solution using MsBuild\u0026rdquo;. Despues, configuramos el paso de la siguiente forma:\nEn el campo \u0026ldquo;MSBuild File\u0026rdquo; se debe especificar el archivo que representa la solución (.sln).\n3. Publicar el Proyecto principal #Para este paso agregamos una opción \u0026ldquo;Build a VisualStudio project or solution using MsBuild\u0026rdquo; y especificamos el Path al proyecto principal de nuestra solucion:\nAdemas ponemos el siguiente argumento:\n/T:Build;Package /p:Configuration=DEBUG /p:OutputPath=\u0026#34;C:\\JenkinsBuilds\\NOMBRE_PROYECTO_PRINCIPAL\u0026#34; /p:DeployIisAppPath=\u0026#34;/Default Web Site/NOMBRE_SITIO\u0026#34; /p:VisualStudioVersion=14.0 4. Desplegar al Servidor IIS #Por ultimo, agregamos otro paso de tipo \u0026ldquo;Ejecutar un comando de Windows\u0026rdquo; y especificamos el siguiente comando:\nxcopy \u0026#34;C:\\JenkinsBuilds\\NOMBRE_SOLUCION\\_PublishedWebsites\\NOMBRE_PROYECTO_PRINCIPAL\u0026#34; /O /X /E /H /K /Y /d \u0026#34;C:\\inetpub\\wwwroot\\NOMBRE_SITIO_EN_IIS\\\u0026#34; Esta instrucción copiará los archivos recurrentemente, pero sólo aquellos que han cambiado recientemente\n5. Compilar AHORA y ver la tendencia de tiempo de ejecución #Una vez esté todo configurado, podemos ordenar a Jenkins realizar la compilación y verificar las estadísticas:\n","date":"June 20, 2017","permalink":"/2017/integracion-jenkins-iis/","section":"Posts","summary":"Estos dias me he planteado realizar la implementacion de CI (Integración Continua) en mis proyectos. Para ello he acudido a Jenkins.\nJenkins # Jenkins nos permite, de una manera facil e intuitiva, programar el despliegue de nuestras aplicaciones. Verán que instalarlo es muy simple. Aquí las instrucciones para instalarlo en cualquier S.O. En mi caso, voy a usarlo sobre Windows Server.\nPor defecto, el puerto por el que escucha Jenkins es el 8080, pero podemos modificarlo para escuchar cualquier otro puerto.","title":"Integración Jenkins con MSBuild e IIS"},{"content":"Ahora mostraré como puedes hacer que tu proyecto WebForms ASP.Net genere cada vez su propia documentación en base a los comentarios del código fuente y además se muestre como un formulario.\nPara empezar #Para ello utilizaremos dos recursos:\nVsXMd de lijunle CommonMark.Net de Knagis Estos dos proyectos los podemos obtener a traves de su respectivo repositorio en GitHub o a traves de Nuget.\nPara nuestro propósito, vamos a trabajar con el Codigo Fuente de VsXMd y con el paquete Nuget de CommonMark.Net\nGenerando la documentación en XML #Como bein sabemos, VisualStudio tiene la posibilidad de generar la documentación de nuestro proyecto en un archivo XML\nLa documentación se genera en base a los comentarios que se realicen sobre los métodos, propiedades y atributos de las clases de nuestro proyecto.\nSi no conoces cómo generar esta documentación, te sugiero que te pases por aqui\nGenerando la documentación en Markdown #Lo primero que tenemos que hacer es abrir de manera separada el proyecto VsXMd. Una vez abierto, podremos compilarlo y generar sus respectivos ejecutables, tal como muestra en la figura:\nNecesitamos tener el ejecutable (y todas sus dependencias) por una razon:vamos a utilizar este ejecutable para generar un Script PostCompilacion en nuestro proyecto que queremos documentar\nAdemás, la razon por la que trabajé con el codigo fuente (y no con el paquete Nuget), fue por que con el codigo fuente tengo la libertad de poder elegir lo que quiero que aparezca en mi documentacion. Asi por ejemplo, no me interesa poner en la documentación los nombres y las descripciones de todos los controles.\nPara hacer este cambio, modifiqué el metodo: private static IEnumerable ToUnits(XElement docElement) en la clase Converter.cs del proyecto Vsxmd para que quede asi:\nprivate static IEnumerable\u0026lt;IUnit\u0026gt; ToUnits(XElement docElement) { // assembly unit var assemblyUnit = new AssemblyUnit(docElement.Element(\u0026#34;assembly\u0026#34;)); // member units var memberUnits = docElement .Element(\u0026#34;members\u0026#34;) .Elements(\u0026#34;member\u0026#34;) .Select(element =\u0026gt; new MemberUnit(element)) .Where(member =\u0026gt; member.Kind != MemberKind.NotSupported \u0026amp;\u0026amp; member.Kind != MemberKind.Constants) .GroupBy(unit =\u0026gt; unit.TypeName) .Select(MemberUnit.ComplementType) .SelectMany(group =\u0026gt; group) .OrderBy(member =\u0026gt; member, MemberUnit.Comparer); // table of contents var tableOfContents = new TableOfContents(memberUnits); return new IUnit[] { tableOfContents } .Concat(new[] { assemblyUnit }) .Concat(memberUnits); } Si se fijan en la clausula Where (y la comparan con la que originalmente esta en el proyecto de lijunle), podrán ver que estoy excluyendo los elementos de tipo Constants.\nAntes:\n.Where(member =\u0026gt; member.Kind != MemberKind.NotSupported) Despues:\n.Where(member =\u0026gt; member.Kind != MemberKind.NotSupported \u0026amp;\u0026amp; member.Kind != MemberKind.Constants) Una vez realizado este cambio, compilo nuevamente el proyecto para obtener el nuevo archivo ejecutable\nCreando el Script PostCompilación en nuestro proyecto #Ahora nos vamos a nuestro proyecto para añadir los scripts post compilación. Primero debemos copiar el ejecutable y las DLLs del proyecto VsXMd a la carpeta de nuestra Solucion para que quede mas o menos asi:\nUna vez tengamos copiados estos archivos, debemos ir a las propiedades de nuestro Proyecto, y nos vamos a la ficha de Eventos de Compilación:\nEn la \u0026ldquo;Linea de comandos del evento posterior a la compilación\u0026rdquo; colocamos:\n\u0026#34;$(SolutionDir)VsXMd\u0026#34;\\Vsxmd.exe \u0026#34;$(ProjectDir)App_Data\\XmlDocument.xml\u0026#34; \u0026#34;$(ProjectDir)App_Data\\XmlDocument.md\u0026#34; En mi caso, cambie la ruta (y el nombre) del archivo XML generado por el VisualStudio, para colocarlo en la carpeta App_Data\nAhora compilamos nuestro Proyecto y verificamos que el archivo Markdown existe en la ruta especificada. Cada vez que compilemos nuestro proyecto, se generará/actualizará la documentación XML y su respectivo archivo MD\nCreando el formulario para mostrar la documentación en HTML a partir del Markdown #Finalmente, podemos hacer que ese archivo Markdown sirva como fuente para uno de nuestros formularios. Aqui es donde utilizaremos el paquete CommonMark para interpretar texto en Markdown y representarlo en HTML\nEn éste proyecto de ejemplo, trabajé con WebForms y Bootstrap. Dentro de un nuevo WebForm (archivo aspx) creo el siguiente bloque de código:\n\u0026lt;div class=\u0026#34;row\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;col-lg-12 col-md-12\u0026#34;\u0026gt; \u0026lt;div runat=\u0026#34;server\u0026#34; id=\u0026#34;divDocs\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; En el CodeBehind, en el evento Page_Load tenemos\nprotected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { //El Markdown var strMarkDown = Server.MapPath(\u0026#34;~/App_Data/XmlDocument.md\u0026#34;); string text = File.ReadAllText(strMarkDown); divDocs.InnerHtml = CommonMarkConverter.Convert(text); } } Y listo!!! Este seria el resultado:\nSi queremos hilar mas fino, podemos modificar el proyecto VsXMd para escribir los textos en español, o para modificar lo que queremos que se muestre.\nEspero les sirva.\n","date":"June 17, 2017","permalink":"/2017/documentar-tu-proyecto-asp.net-y-mostrarlo-como-un-formulario-mas/","section":"Posts","summary":"Ahora mostraré como puedes hacer que tu proyecto WebForms ASP.Net genere cada vez su propia documentación en base a los comentarios del código fuente y además se muestre como un formulario.\nPara empezar #Para ello utilizaremos dos recursos:\nVsXMd de lijunle CommonMark.Net de Knagis Estos dos proyectos los podemos obtener a traves de su respectivo repositorio en GitHub o a traves de Nuget.\nPara nuestro propósito, vamos a trabajar con el Codigo Fuente de VsXMd y con el paquete Nuget de CommonMark.","title":"Documentar tu proyecto ASP.Net y mostrarlo como un formulario mas"},{"content":"","date":null,"permalink":"/tags/csharp/","section":"Tags","summary":"","title":"Csharp"},{"content":"En mi empresa hemos empezado a trabajar con OpenLDAP, y esto implica cambiar todos los metodos de autenticacion de los sistemas desarrollados, a éste protocolo.\nAl principio parecia dificil, pero no fue asi. Todo se hizo mas facil con la ayuda de algunos articulos de stackoverflow.\nAl final pude armar una clase helper que me permitiera acceder a los elementos del LDAP:\npublic class LDAPHelper { private readonly LdapConnection ldapConnection; private readonly string searchBaseDN; private readonly int pageSize; public LDAPHelper( string searchBaseDN, string hostName, int portNumber, AuthType authType, string connectionAccountName, string connectionAccountPassword, int pageSize) { var ldapDirectoryIdentifier = new LdapDirectoryIdentifier( hostName, portNumber, true, false); var networkCredential = new NetworkCredential( connectionAccountName, connectionAccountPassword); ldapConnection = new LdapConnection( ldapDirectoryIdentifier, networkCredential) { AuthType = authType }; ldapConnection.SessionOptions.ProtocolVersion = 3; this.searchBaseDN = searchBaseDN; this.pageSize = pageSize; } public IEnumerable\u0026lt;SearchResultEntryCollection\u0026gt; PagedSearch( string searchFilter, string[] attributesToLoad) { var pagedResults = new List\u0026lt;SearchResultEntryCollection\u0026gt;(); var searchRequest = new SearchRequest (searchBaseDN, searchFilter, SearchScope.Subtree, attributesToLoad); var searchOptions = new SearchOptionsControl(SearchOption.DomainScope); searchRequest.Controls.Add(searchOptions); var pageResultRequestControl = new PageResultRequestControl(pageSize); searchRequest.Controls.Add(pageResultRequestControl); while (true) { var searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest); var pageResponse = (PageResultResponseControl)searchResponse.Controls[0]; yield return searchResponse.Entries; if (pageResponse.Cookie.Length == 0) break; pageResultRequestControl.Cookie = pageResponse.Cookie; } } } Con ésta clase, la consulta a los elementos del LDAP fue sencilla:\nstatic void Main(string[] args) { try { var baseOfSearch = \u0026#34;dc=integrate,dc=com,dc=bo\u0026#34;; var ldapHost = \u0026#34;192.168.0.101\u0026#34;; var ldapPort = 389; var connectAsDN = \u0026#34;cn=admin,dc=integrate,dc=com,dc=bo\u0026#34;; var pageSize = 1000; var secureString = \u0026#34;CONTRASEÑA_ADMIN_LDAP\u0026#34;; var openLDAPHelper = new LDAPHelper( baseOfSearch, ldapHost, ldapPort, AuthType.Basic, connectAsDN, secureString, pageSize); var searchFilter = \u0026#34;objectclass=posixAccount\u0026#34;; //var searchFilter = \u0026#34;uid=rvera\u0026#34;; var attributesToLoad = new[] { \u0026#34;sn\u0026#34;,\u0026#34;uid\u0026#34;,\u0026#34;cn\u0026#34;,\u0026#34;userPassword\u0026#34; }; var pagedSearchResults = openLDAPHelper.PagedSearch( searchFilter, attributesToLoad); foreach (var searchResultEntryCollection in pagedSearchResults) foreach (SearchResultEntry searchResultEntry in searchResultEntryCollection) { Console.WriteLine(searchResultEntry.Attributes[\u0026#34;uid\u0026#34;][0] + \u0026#34;: \u0026#34; + searchResultEntry.Attributes[\u0026#34;cn\u0026#34;][0]); Console.WriteLine(searchResultEntry.Attributes[\u0026#34;userPassword\u0026#34;][0]); Console.WriteLine(\u0026#34;.......\u0026#34;); } } catch (Exception exp) { Console.WriteLine(exp.Message); Console.WriteLine(exp.StackTrace); } Console.WriteLine(\u0026#34;Presione una tecla para terminar...\u0026#34;); Console.Read(); } Pueden obtener el ejemplo aqui\n","date":"January 4, 2017","permalink":"/2017/openldap-con-csharp/","section":"Posts","summary":"En mi empresa hemos empezado a trabajar con OpenLDAP, y esto implica cambiar todos los metodos de autenticacion de los sistemas desarrollados, a éste protocolo.\nAl principio parecia dificil, pero no fue asi. Todo se hizo mas facil con la ayuda de algunos articulos de stackoverflow.\nAl final pude armar una clase helper que me permitiera acceder a los elementos del LDAP:\npublic class LDAPHelper { private readonly LdapConnection ldapConnection; private readonly string searchBaseDN; private readonly int pageSize; public LDAPHelper( string searchBaseDN, string hostName, int portNumber, AuthType authType, string connectionAccountName, string connectionAccountPassword, int pageSize) { var ldapDirectoryIdentifier = new LdapDirectoryIdentifier( hostName, portNumber, true, false); var networkCredential = new NetworkCredential( connectionAccountName, connectionAccountPassword); ldapConnection = new LdapConnection( ldapDirectoryIdentifier, networkCredential) { AuthType = authType }; ldapConnection.","title":"OpenLDAP con CSharp"},{"content":"Hace algun tiempo tuve la necesidad de descargar las imágenes de una página web. Esto sería tarea sencilla de no ser porque existian mas de 50 imágenes en esa página. ASi que me propuse a realizar una aplicación de escritorio que se encargue de realizar ese trabajo por mi.\nPueden encontrar la aplicación y el codigo fuente aqui.\nMe comentan que tal les va.\n","date":"December 8, 2016","permalink":"/2016/aplicacion-para-descargar-imagenes-de-una-p%C3%A1gina-web/","section":"Posts","summary":"Hace algun tiempo tuve la necesidad de descargar las imágenes de una página web. Esto sería tarea sencilla de no ser porque existian mas de 50 imágenes en esa página. ASi que me propuse a realizar una aplicación de escritorio que se encargue de realizar ese trabajo por mi.\nPueden encontrar la aplicación y el codigo fuente aqui.\nMe comentan que tal les va.","title":"Aplicacion para descargar imagenes de una página web"},{"content":"Tenemos un repositorio FTP donde varias oficinas en distintos lugares van alojando archivos PDF que generan con información de su respectivo trabajo. Basicamente todos pueden acceder al FTP y consultar los documentos subidos.\nSin embargo, un nuevo requerimiento necesitaba revisar y calificar el documento. Para ello, se necesitaba que el sistema web que se maneja, muestre los archivos PDF desde el FTP (sin necesidad de descargarlos todos al servidor web).\nBuscando, pude encontrar soluciones parciales, pero al final llegué a este bloque de código que me permitía visualizar el archivo requerido:\nFileInfo objFile = new FileInfo(filename); FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri(\u0026#34;ftp://\u0026#34; + ftpServerIP + \u0026#34;/\u0026#34; + filename)); request.Credentials = new NetworkCredential(Ftp_Login_Name, Ftp_Login_Password); FtpWebResponse response = (FtpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); StreamReader reader = new StreamReader(responseStream); byte[] bytes = null; using (var memstream = new MemoryStream()) { reader.BaseStream.CopyTo(memstream); bytes = memstream.ToArray(); } Response.Clear(); Response.ClearHeaders(); Response.ClearContent(); Response.Cache.SetCacheability(HttpCacheability.NoCache); Response.ContentType = \u0026#34;application/pdf\u0026#34;; Response.AddHeader(\u0026#34;Content-Disposition\u0026#34;, \u0026#34;inline; filename=\u0026#34; + objFile.Name); Response.BinaryWrite(bytes); Response.End(); ","date":"August 6, 2016","permalink":"/2016/mostrar-archivo-pdf-alojado-en-un-servidor-ftp/","section":"Posts","summary":"Tenemos un repositorio FTP donde varias oficinas en distintos lugares van alojando archivos PDF que generan con información de su respectivo trabajo. Basicamente todos pueden acceder al FTP y consultar los documentos subidos.\nSin embargo, un nuevo requerimiento necesitaba revisar y calificar el documento. Para ello, se necesitaba que el sistema web que se maneja, muestre los archivos PDF desde el FTP (sin necesidad de descargarlos todos al servidor web).","title":"Mostrar archivo PDF alojado en un servidor FTP"},{"content":"Para facilitar la tarea de crear reportes o documentos en formato XLS o XLSX, se tiene el paquete ClosedXML. El mismo tiene soporte para el uso de templates.\nLo primero que tenemos que hacer es instalar el paquete Nuget en nuestro proyecto:\nPM\u0026gt; Install-Package ClosedXML Después, el bloque de codigo necesario para mostrar un excel a partir de una plantilla es:\nvar strTitulo = \u0026#34;Titulo del reporte\u0026#34;; var dtReporte = ObtenerDatosReporte(); //Funcion que devolverá un DataTable //Definimos la plantilla y la utilizamos con la libreria ClosedXML var template = Server.MapPath(\u0026#34;~/doc_templates/Reporte.xlsx\u0026#34;); using (var wb = new XLWorkbook(template)) { //Ponemos algunos valores en el documento wb.Worksheets.Worksheet(1).Cell(5, 1).Value = strTitulo; //Podemos insertar un DataTable wb.Worksheets.Worksheet(1).Cell(9, 1).InsertTable(dtReporte); //Aplicamos los filtros y formatos a la tabla wb.Worksheets.Worksheet(1).Table(\u0026#34;Table1\u0026#34;).ShowAutoFilter = true; wb.Worksheets.Worksheet(1).Table(\u0026#34;Table1\u0026#34;).Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; wb.Worksheets.Worksheet(1).Columns(2, 2 + dtReporte.Columns.Count).AdjustToContents(); //Limitamos el ancho de las columnas a 60 foreach (var column in wb.Worksheets.Worksheet(1).Columns()) if (column.Width \u0026gt; 60) { column.Width = 60; column.Style.Alignment.WrapText = true; } wb.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; wb.Style.Font.Bold = true; //Enviamos el archivo al cliente Response.Clear(); Response.Buffer = true; Response.Charset = \u0026#34;\u0026#34;; Response.ContentType = \u0026#34;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\u0026#34;; Response.AddHeader(\u0026#34;content-disposition\u0026#34;, \u0026#34;attachment;filename=\\\u0026#34;\u0026#34; + strTitulo + \u0026#34;.xlsx\\\u0026#34;\u0026#34;); using (var myMemoryStream = new MemoryStream()) { wb.SaveAs(myMemoryStream); myMemoryStream.WriteTo(Response.OutputStream); Response.Flush(); Response.End(); } } Para acceder a la documentación del proyecto, pueden ingresar a su repositorio\n","date":"April 26, 2016","permalink":"/2016/closedxml-y-el-uso-de-plantillas/","section":"Posts","summary":"Para facilitar la tarea de crear reportes o documentos en formato XLS o XLSX, se tiene el paquete ClosedXML. El mismo tiene soporte para el uso de templates.\nLo primero que tenemos que hacer es instalar el paquete Nuget en nuestro proyecto:\nPM\u0026gt; Install-Package ClosedXML Después, el bloque de codigo necesario para mostrar un excel a partir de una plantilla es:\nvar strTitulo = \u0026#34;Titulo del reporte\u0026#34;; var dtReporte = ObtenerDatosReporte(); //Funcion que devolverá un DataTable //Definimos la plantilla y la utilizamos con la libreria ClosedXML var template = Server.","title":"ClosedXML y el uso de plantillas"},{"content":"En éste tutorial se va a mostrar cómo pueden enlazarse tres servicios para lograr una infraestructura que permita automatizar varias tareas en la cadena de desarrollo de software.\nPrimero necesitamos tener instalado Git, además de tener un repositorio localmente (en nuestro equipo) para alojarlo remotamente en BitBucket.\nUna vez tengamos el repositorio en BitBucket, nos dirigimos a AppHarbor para crear nuestra aplicacion.\nUna vez se haya creado la aplicación en AppHarbor, se debe elegir la opcion de despliegue desde BitBucket (\u0026ldquo;Configure BitBucket to deploy to AppHarbor\u0026rdquo;).\nCuando presionemos ése enlace, se nos pedir conectar nuestra cuenta de BitBucket, y acto seguido, nos pedirá el repositorio que queremos vincular\nSi todo va bien, en BitBucket podremos ver que se han añadido permisos a nuestro repositorio:\nY tambien se ha añadido automáticamente un Hook\nY listo!!! Cada vez que hagamos un PUSH desde nuestro proyecto local, se realizará una compilación en AppHarbor, poniendo a nuestra disponibilidad el proyecto compilado:\nSi el proyecto es una aplicación de escritorio, se creará un enlace de descarga del Ejecutable y todas sus dependencias. En cambio, si se trata de un proyecto Web (o WCF), se creará un directorio donde puede probarse la aplicación:\nTodo éste proceso automatiza el despliegue de aplicaciones, gracias a los WebHooks que se tienen a la mano en BitBucket.\nY es precisamente con éstos WebHooks que se va arealizar la integración con Trello. Podemos crear un Hook de tipo Mail en BitBucket de tal manera que se envie un correo a la dirección de nuestro tablero en Trello.\nPersonalmente configuro todos mis PUSH para que se \u0026ldquo;inserten\u0026rdquo; en la lista de \u0026ldquo;Testing\u0026rdquo; de mi tablero.\n","date":"February 24, 2015","permalink":"/2015/integrando-trello--bitbucket--appharbor/","section":"Posts","summary":"En éste tutorial se va a mostrar cómo pueden enlazarse tres servicios para lograr una infraestructura que permita automatizar varias tareas en la cadena de desarrollo de software.\nPrimero necesitamos tener instalado Git, además de tener un repositorio localmente (en nuestro equipo) para alojarlo remotamente en BitBucket.\nUna vez tengamos el repositorio en BitBucket, nos dirigimos a AppHarbor para crear nuestra aplicacion.\nUna vez se haya creado la aplicación en AppHarbor, se debe elegir la opcion de despliegue desde BitBucket (\u0026ldquo;Configure BitBucket to deploy to AppHarbor\u0026rdquo;).","title":"Integrando Trello + Bitbucket + AppHarbor"},{"content":"Hoy me he visto en la necesidad de obtener Varias fechas de una tabla en PostgreSql y mostrar cada una con un color diferente en un Sistema Web.\nMi primera alternativa era buscar una funcion en PHP que me permitiera generar colores hexadecimales aleatorios, pero como tengo más experiencia en PLPgSql, decidí empezar por ahí.\nPrimero lo primero: todos sabemos que un color hexadecimal es la presentacion de 3 numeros en el rango de 0-255 convertidos a base 16. Pues bien por ahí es por donde empece: generar un numero aleatorio en el rango de 0 a 255.\nSELECT trunc(random() * 255)::INTEGER Ahora necesitamos una función que convierta un numero de Base 10 a Base 16: Para ello necesitamos la siguiente funcion:\nCREATE OR REPLACE FUNCTION b10_b16(digits bigint, min_width integer DEFAULT 0) RETURNS character varying AS $BODY$ DECLARE chars char[]; ret varchar; val bigint; BEGIN chars:=ARRAY[\u0026#39;0\u0026#39;,\u0026#39;1\u0026#39;,\u0026#39;2\u0026#39;,\u0026#39;3\u0026#39;,\u0026#39;4\u0026#39;,\u0026#39;5\u0026#39;,\u0026#39;6\u0026#39;,\u0026#39;7\u0026#39;,\u0026#39;8\u0026#39;,\u0026#39;9\u0026#39;,\u0026#39;A\u0026#39;,\u0026#39;B\u0026#39;,\u0026#39;C\u0026#39;,\u0026#39;D\u0026#39;,\u0026#39;E\u0026#39;,\u0026#39;F\u0026#39;]; val := digits; ret := \u0026#39;\u0026#39;; IF val \u0026lt; 0 THEN val := val * -1; END IF; WHILE val != 0 LOOP ret := chars[(val % 16)+1] || ret; val := val / 16; END LOOP; IF min_width \u0026gt; 0 AND char_length(ret) \u0026lt; min_width THEN ret := lpad(ret, min_width, \u0026#39;0\u0026#39;); END IF; RETURN ret; END; $BODY$ LANGUAGE plpgsql IMMUTABLE COST 100; El uso de ésta función es:\nSELECT b10_b16(45); --Devolverá el Dato 2D SELECT b10_b16(11); --Devolverá el Dato B SELECT b10_b16(11,2) --Devolverá el Dato 0B Con todo ésto tenemos la Base para generar un color aleatorio:\nSELECT \u0026#39;#\u0026#39; || b10_b16(trunc(random() * 255)::INTEGER,2) || b10_b16(trunc(random() * 255)::INTEGER,2) || b10_b16(trunc(random() * 255)::INTEGER,2) AS color --Devolvera algo así: #69A51D Y listo!!!\nYa tenemos nuestra función que genera colores hexadecimales ALEATORIAMENTE!!!\n","date":"November 13, 2014","permalink":"/2014/generar-colores-hexadecimales-aleatorios-con-postgres/","section":"Posts","summary":"Hoy me he visto en la necesidad de obtener Varias fechas de una tabla en PostgreSql y mostrar cada una con un color diferente en un Sistema Web.\nMi primera alternativa era buscar una funcion en PHP que me permitiera generar colores hexadecimales aleatorios, pero como tengo más experiencia en PLPgSql, decidí empezar por ahí.\nPrimero lo primero: todos sabemos que un color hexadecimal es la presentacion de 3 numeros en el rango de 0-255 convertidos a base 16.","title":"Generar colores hexadecimales aleatorios con Postgres"},{"content":"","date":null,"permalink":"/tags/sql/","section":"Tags","summary":"","title":"Sql"},{"content":"Ahora les voy a hablar de un servicio GRATUITO por demás interesante. GPSVisualizer nos permite realizar conversiones entre diversos formatos de archivos de mapas (GPS, GPX, KML, KMZ, etc).\nAdemás, permite la visualización de éstos archivos. Realmente es una herramienta muy util. Puedes revisarla aqui.\n","date":"November 12, 2014","permalink":"/2014/gps-visualizer/","section":"Posts","summary":"Ahora les voy a hablar de un servicio GRATUITO por demás interesante. GPSVisualizer nos permite realizar conversiones entre diversos formatos de archivos de mapas (GPS, GPX, KML, KMZ, etc).\nAdemás, permite la visualización de éstos archivos. Realmente es una herramienta muy util. Puedes revisarla aqui.","title":"GPS Visualizer"},{"content":"","date":null,"permalink":"/tags/herramientas/","section":"Tags","summary":"","title":"Herramientas"},{"content":"¿Quién nunca ha tenido la necesidad de exportar un reporte tabulado a MS Excel?\nEn la red podemos encontrar muchas alternativas para lograr éste objetivo: librerías para PHP, módulos para .Net , etc.\nSin embargo, una solución para cualquier plataforma de desarrollo Web viene de la mano de jQuery. Gracias a un plugin que permite copiar el contenido de una tabla a partir de su ID.\nPara acceder a éste plugin, podemos descargar los archivos necesarios desde aqui.\nSi bien en ése sitio hay opciones para poder exportar una tabla a varios formatos (PDF, CSV, PNG, etc.), lo que se va a explicar aquí es la forma de exportar una Tabla a Excel:\nUna vez tengamos los archivos y se hayan subido a nuestro respectivo servidor web, debemos referenciarlos en el header de la página donde se tiene la tabla para exportar:\n\u0026lt;script type=\u0026#34;text/javascript\u0026#34; src=\u0026#34;tableExport.js\u0026#34;\u0026gt; \u0026lt;script type=\u0026#34;text/javascript\u0026#34; src=\u0026#34;jquery.base64.js\u0026#34;\u0026gt; Luego, en alguna parte de nuestra página, creamos un Boton para exportar nuestra tabla:\n\u0026lt;button class=\u0026#34;btn btn-mini download-image\u0026#34; title=\u0026#34;Descargar Excel\u0026#34; onClick=\u0026#34;$(\u0026#39;#id_tabla\u0026#39;).tableExport({type:\u0026#39;excel\u0026#39;,escape:\u0026#39;false\u0026#39;});\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;\u0026#39;.base_url().\u0026#39;img/excel.png\u0026#34; /\u0026gt; \u0026lt;/button\u0026gt; Recordar que nuestra tabla de tener el id \u0026ldquo;id_tabla\u0026rdquo;:\n\u0026lt;table id=\u0026#34;id_tabla\u0026#34; class=\u0026#34;table table-striped\u0026#34;\u0026gt; ... \u0026lt;/table\u0026gt; Con éso ya tenemos nuestro exportador de Tablas a Excel\n","date":"November 12, 2012","permalink":"/2012/exportar-tabla-con-jquery/","section":"Posts","summary":"¿Quién nunca ha tenido la necesidad de exportar un reporte tabulado a MS Excel?\nEn la red podemos encontrar muchas alternativas para lograr éste objetivo: librerías para PHP, módulos para .Net , etc.\nSin embargo, una solución para cualquier plataforma de desarrollo Web viene de la mano de jQuery. Gracias a un plugin que permite copiar el contenido de una tabla a partir de su ID.\nPara acceder a éste plugin, podemos descargar los archivos necesarios desde aqui.","title":"Exportar tabla con JQuery"},{"content":"","date":null,"permalink":"/tags/web/","section":"Tags","summary":"","title":"Web"},{"content":"","date":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories"}]