feat: Primeira Versão traduzida

This commit is contained in:
Ricardo Carneiro 2025-12-21 21:57:39 -03:00
parent 2c86b81f7f
commit 1141594d20
147 changed files with 97502 additions and 1 deletions

25
.dockerignore Normal file
View File

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

14
CarneiroTech.csproj Normal file
View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.44.0" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>
</Project>

25
CarneiroTech.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.1.11304.174 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarneiroTech", "CarneiroTech.csproj", "{BFC5463B-C1CF-69EE-98AF-C5790D1C99C2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BFC5463B-C1CF-69EE-98AF-C5790D1C99C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BFC5463B-C1CF-69EE-98AF-C5790D1C99C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFC5463B-C1CF-69EE-98AF-C5790D1C99C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFC5463B-C1CF-69EE-98AF-C5790D1C99C2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9322B71E-A202-497C-8EB5-48B61812278D}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,329 @@
---
title: "ASP 3.0 to .NET Core Migration - Cargo Tracking System"
slug: "asp-to-dotnet-migration"
summary: "Tech Lead in gradual migration of mission-critical ASP 3.0 system to .NET Core, with dual-write data synchronization and cost reduction of $20k/year in mapping APIs."
client: "Logistics and Tracking Company"
industry: "Logistics & Security"
timeline: "12 months (complete migration)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- ASP Classic
- .NET Core
- SQL Server
- Migration
- Tech Lead
- OSRM
- APIs
- Architecture
featured: true
order: 2
date: 2015-06-01
seo_title: "ASP 3.0 to .NET Core Migration - Carneiro Tech Case Study"
seo_description: "Case study of gradual ASP 3.0 to .NET Core migration with data synchronization and $20k/year cost savings in API expenses."
seo_keywords: "ASP migration, .NET Core, legacy modernization, SQL Server, OSRM, tech lead, routing API"
---
## Overview
Mission-critical cargo monitoring system for high-value loads (LED TVs worth $600 each, shipments up to 1000 units) using GPS satellite tracking. The application covered the entire lifecycle: from driver registration and evaluation (police background checks) to real-time monitoring and final delivery.
**Main challenge:** Migrate legacy ASP 3.0 application to .NET Core with zero downtime, maintaining 24/7 critical operations.
---
## Challenge
### Critical Legacy System
The company operated a mission-critical system in **ASP 3.0** (Classic ASP) that couldn't stop:
**Legacy technology:**
- ASP 3.0 (1998 technology)
- SQL Server 2005
- On-premises failover cluster (perfectly capable of handling the load)
- Integration with GPS satellite trackers
- Google Maps API (cost: **$20,000/year** just for route calculation)
**Constraints:**
- 24/7 system operation with high-value cargo
- No downtime allowed during migration
- Multiple interdependent modules
- Team needed to continue developing features during migration
---
## Solution Architecture
### Phase 1: Infrastructure Preparation (Months 1-3)
#### Database Upgrade
```
SQL Server 2005 → SQL Server 2014
- Full backup and validation
- Stored procedure migration
- Index optimization
- Performance testing
```
#### Dual-Write Synchronization Strategy
I implemented a **bidirectional synchronization system** that allowed:
1. **New modules (.NET Core)** wrote to the new database
2. **Automatic trigger** synchronized data to the legacy database
3. **Old modules (ASP 3.0)** continued working normally
4. **Zero downtime** throughout the entire migration
```csharp
// Synchronization implementation example
public class DualWriteService
{
public async Task SaveDriver(Driver driver)
{
// Write to new database (.NET Core)
await _newDbContext.Drivers.AddAsync(driver);
await _newDbContext.SaveChangesAsync();
// SQL trigger automatically syncs to legacy database
// ASP 3.0 modules continue functioning
}
}
```
**Why this approach?**
- Enabled **module-by-module** migration
- Team could continue developing
- Simple rollback if needed
- Reduced operational risk
---
### Phase 2: Gradual Module Migration (Months 4-12)
I migrated modules in increasing complexity order:
**Migration sequence:**
1. ✅ Basic registrations (drivers, vehicles)
2. ✅ Risk assessment (police database integration)
3. ✅ Cargo and route management
4. ✅ Real-time GPS monitoring
5. ✅ Alerts and notifications
6. ✅ Reports and analytics
**Migrated application stack:**
- `.NET Core 1.0` (2015-2016 was the beginning of .NET Core)
- `Entity Framework Core`
- `SignalR` for real-time monitoring
- `SQL Server 2014`
- RESTful APIs
---
### Phase 3: Cost Reduction with OSRM ($20k/year Savings)
#### Problem: Prohibitive Google Maps Cost
The company spent **$20,000/year** just on Google Maps Directions API for truck route calculation.
#### Solution: OSRM (Open Source Routing Machine)
I implemented a solution based on **OSRM** (open-source routing engine):
**Solution architecture:**
```
┌─────────────────┐
│ Frontend │
│ (Leaflet.js) │
└────────┬────────┘
┌─────────────────┐ ┌──────────────┐
│ API Wrapper │─────▶│ OSRM Server │
│ (.NET Core) │ │ (self-hosted)│
└────────┬────────┘ └──────────────┘
┌─────────────────┐
│ Google Maps │
│ (display only) │
└─────────────────┘
```
**Implementation:**
1. **OSRM Server configured** on own server
2. **User-friendly API wrapper** in .NET Core that:
- Received origin/destination
- Queried OSRM (free)
- Returned all route points
- Formatted for frontend
3. **Frontend** drew the route on Google Maps (visualization only, no routing API)
```csharp
[HttpGet("route")]
public async Task<IActionResult> GetRoute(double originLat, double originLng,
double destLat, double destLng)
{
// Query OSRM (free)
var osrmResponse = await _osrmClient.GetRouteAsync(
originLat, originLng, destLat, destLng);
// Return formatted points for frontend
return Ok(new {
points = osrmResponse.Routes[0].Geometry.Coordinates,
distance = osrmResponse.Routes[0].Distance,
duration = osrmResponse.Routes[0].Duration
});
}
```
**Frontend with Leaflet:**
```javascript
// Draw route on map (Google Maps only for tiles)
L.polyline(routePoints, {color: 'red'}).addTo(map);
```
#### OpenStreetMap Attempt
I tried to also replace Google Maps (tiles) with **OpenStreetMap**, which worked technically, but:
**Users didn't like** the appearance
❌ Preferred the familiar Google Maps interface
**Decision:** Keep Google Maps for visualization only (no routing API cost)
**Result:** Savings of **~$20,000/year** while maintaining route quality.
---
## Results & Impact
### Complete Migration in 12 Months
**100% of modules** migrated from ASP 3.0 to .NET Core
**Zero downtime** throughout the entire migration
**Productive team** throughout the process
✅ Faster and more scalable system
### Cost Reduction
💰 **$20,000/year saved** by replacing Google Maps Directions API
📉 **Optimized infrastructure** with SQL Server 2014
### Technical Improvements
🚀 **Performance:** .NET Core application 3x faster than ASP 3.0
🔒 **Security:** Modern stack with active security patches
🛠️ **Maintainability:** Modern C# code vs legacy VBScript
📊 **Monitoring:** SignalR for more efficient real-time tracking
---
## Unexecuted Phase: Microservices & Cloud
### Initial Planning
I participated in the **design and conception** of the second phase (never executed):
**Planned architecture:**
- Migration to **Azure** (cloud was just starting in 2015)
- Break into **microservices**:
- Authentication service
- GPS/tracking service
- Routing service
- Notification service
- **Event-driven architecture** with message queues
**Why it wasn't executed:**
I left the company right after completing the .NET Core migration. The second phase was planned but not implemented by me.
---
## Tech Stack
`ASP 3.0` `VBScript` `.NET Core 1.0` `C#` `Entity Framework Core` `SQL Server 2005` `SQL Server 2014` `OSRM` `Leaflet.js` `Google Maps` `SignalR` `REST APIs` `GPS/Satellite` `Migration Strategy` `Dual-Write Pattern`
---
## Key Decisions & Trade-offs
### Why dual-write synchronization?
**Alternatives considered:**
1. ❌ Big Bang migration (too risky)
2. ❌ Keep everything in ASP 3.0 (unsustainable)
3. ✅ **Gradual migration with sync** (chosen)
**Rationale:**
- Critical system couldn't stop
- Enabled module-by-module rollback
- Team remained productive
### Why OSRM instead of others?
**Alternatives:**
- Google Maps: $20k/year ❌
- Mapbox: Paid license ❌
- GraphHopper: Complex setup ❌
- **OSRM: Open-source, fast, configurable**
### Why not OpenStreetMap for tiles?
**UX-based decision:**
- Technically worked perfectly
- Users preferred familiar Google interface
- **Compromise:** Google Maps for visualization (free) + OSRM for routing (free)
---
## Lessons Learned
### 1. Gradual Migration > Big Bang
Migrating module by module with synchronization enabled:
- Continuous learning
- Route adjustments during the process
- Team and stakeholder confidence
### 2. Open Source Can Save a Lot
OSRM saved **$20k/year** without quality loss. But requires:
- Expertise to configure
- Own infrastructure
- Continuous maintenance
### 3. UX > Technology Sometimes
OpenStreetMap was technically superior (free), but users preferred Google Maps. **Lesson:** Listen to end users.
### 4. Plan for Cloud, but Validate ROI
In 2015, cloud was just starting. On-premises infrastructure (SQL Server cluster) was perfectly capable. **Don't force cloud if there's no clear benefit.**
---
## Context: Why 2015 Was a Special Moment?
**State of technology in 2015:**
- ☁️ **Cloud in early stages:** AWS existed, Azure growing, but low corporate adoption
- 🆕 **.NET Core 1.0 launched** in June 2016 (we used RC during the project)
- 📱 **Microservices:** New concept, Docker in early adoption
- 🗺️ **Google Maps dominant:** Paid APIs, few mature open-source alternatives
**Challenges of the time:**
- Non-existent ASP→.NET migration tools
- Scarce .NET Core documentation (version 1.0!)
- Architecture patterns still consolidating
This project was **pioneering** in adopting .NET Core right at the beginning, when most were migrating to .NET Framework 4.x.
---
**Result:** Successful migration of 24/7 critical system, $20k/year savings, and solid foundation for future evolution.
[Want to discuss a similar migration? Get in touch](#contact)

View File

@ -0,0 +1,382 @@
---
title: "CNPJ Fast - Alphanumeric CNPJ Migration Process"
slug: "cnpj-fast-process"
summary: "Creation of structured methodology for migrating applications to the new Brazilian alphanumeric CNPJ format, sold to insurance company and collection agency."
client: "Consulting Firm (Internal)"
industry: "Consulting & Digital Transformation"
timeline: "3 months (process creation)"
role: "Solution Architect & Process Designer"
image: ""
tags:
- Process Design
- CNPJ
- Migration Strategy
- Regulatory Compliance
- Consulting
- Sales Enablement
featured: true
order: 3
date: 2024-09-01
seo_title: "CNPJ Fast - Alphanumeric CNPJ Migration Methodology"
seo_description: "Case study of creating a structured process for migrating to Brazilian alphanumeric CNPJ, sold to insurance company and collection agency."
seo_keywords: "CNPJ alphanumeric, migration process, regulatory compliance, consulting, methodology"
---
## Overview
With the introduction of **alphanumeric CNPJ** by the Brazilian Federal Revenue Service, companies faced the challenge of adapting their legacy applications that stored CNPJ as numeric fields (`bigint`, `numeric`, `int`).
I created **CNPJ Fast**, a structured methodology to assess, plan, and execute CNPJ migrations in corporate applications and databases.
**Result:** Process sold to **2 clients** (insurance company and collection agency) before implementation.
---
## Challenge
### Complex Regulatory Change
**Regulatory context:**
- Brazilian Federal Revenue Service introduced **alphanumeric CNPJ**
- CNPJ is no longer just numbers (14 digits)
- Now accepts **letters and numbers** (alphanumeric format)
**Impact on companies:**
```sql
-- BEFORE: Numeric CNPJ
CNPJ BIGINT -- 12345678000190
-- AFTER: Alphanumeric CNPJ
CNPJ VARCHAR(18) -- 12.ABC.678/0001-90
```
**Identified problems:**
1. **Database:** `BIGINT`, `NUMERIC`, `INT` columns don't support characters
2. **Primary keys:** CNPJ used as PK in several tables
3. **Foreign keys:** Relationships between tables
4. **Volume:** Millions of records to migrate
5. **Applications:** Validations, masks, business rules
6. **Testing:** Ensure integrity after migration
7. **Downtime:** Limited maintenance windows
**Without a structured process**, companies risked:
- Data loss
- Database inconsistencies
- Broken applications
- Extended downtime
---
## Solution: CNPJ Fast Process
### 5-Phase Methodology
I designed a structured process that could be replicated across different clients:
```
┌─────────────────────────────────────────────┐
│ PHASE 1: DISCOVERY & ASSESSMENT │
│ - Application inventory │
│ - Database schema analysis │
│ - Identification of impacted tables │
│ - Data volume estimation │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 2: IMPACT ANALYSIS │
│ - Dependency mapping │
│ - Analysis of primary/foreign keys │
│ - Identification of business rules │
│ - Risk assessment │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 3: MIGRATION PLANNING │
│ - Migration strategy (phased commits) │
│ - Automated SQL scripts │
│ - Rollback plan │
│ - Maintenance windows │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 4: EXECUTION │
│ - Batch data migration │
│ - Application updates │
│ - Integration testing │
│ - Integrity validation │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PHASE 5: VALIDATION & GO-LIVE │
│ - Regression testing │
│ - Performance validation │
│ - Coordinated go-live │
│ - Post-migration monitoring │
└─────────────────────────────────────────────┘
```
---
### Phase 1: Discovery & Assessment
**Objective:** Understand the complete migration scope
**Deliverables:**
1. **Application Inventory**
- List of applications using CNPJ
- Technologies (ASP 3.0, VB6, .NET, microservices)
- Criticality of each application
2. **Schema Analysis**
```sql
-- Automated discovery script
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
ON t.TABLE_NAME = c.TABLE_NAME
WHERE c.COLUMN_NAME LIKE '%CNPJ%'
AND c.DATA_TYPE IN ('bigint', 'numeric', 'int')
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME;
```
3. **Volume Estimation**
- Total records per table
- Size in GB
- Estimated migration time
**Example output:**
| Table | Column | Current Type | Records | Criticality |
|--------|--------|------------|-----------|-------------|
| Clients | CNPJ_Client | BIGINT | 8,000,000 | High |
| Suppliers | CNPJ_Supplier | NUMERIC(14) | 2,500,000 | Medium |
| Transactions | CNPJ_Payer | BIGINT | 90,000,000 | Critical |
---
### Phase 2: Impact Analysis
**Objective:** Map all dependencies and risks
**Key analysis:**
```sql
-- Identifies PKs and FKs involving CNPJ
SELECT
fk.name AS FK_Name,
tp.name AS Parent_Table,
cp.name AS Parent_Column,
tr.name AS Referenced_Table,
cr.name AS Referenced_Column
FROM sys.foreign_keys fk
INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
INNER JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id
AND fkc.parent_object_id = cp.object_id
INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id
AND fkc.referenced_object_id = cr.object_id
WHERE cp.name LIKE '%CNPJ%' OR cr.name LIKE '%CNPJ%';
```
**Risk Assessment:**
- **High:** Tables with CNPJ as PK and >10M records
- **Medium:** Tables with FK to CNPJ
- **Low:** Tables without constraints
---
### Phase 3: Migration Planning
**Gradual migration strategy:**
To avoid database locks, I designed a **phased commits** strategy:
```sql
-- Strategy for large tables (>1M records)
-- 1. Add new VARCHAR column
ALTER TABLE Clients
ADD CNPJ_Client_New VARCHAR(18) NULL;
-- 2. Migration in batches (phased commits)
DECLARE @BatchSize INT = 100000;
DECLARE @RowsAffected INT = 1;
WHILE @RowsAffected > 0
BEGIN
UPDATE TOP (@BatchSize) Clients
SET CNPJ_Client_New = FORMAT(CNPJ_Client, '00000000000000')
WHERE CNPJ_Client_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
WAITFOR DELAY '00:00:01'; -- Pause between batches
END;
-- 3. Remove constraints (PKs, FKs)
ALTER TABLE Clients DROP CONSTRAINT PK_Clients;
-- 4. Rename columns
EXEC sp_rename 'Clients.CNPJ_Client', 'CNPJ_Client_Old', 'COLUMN';
EXEC sp_rename 'Clients.CNPJ_Client_New', 'CNPJ_Client', 'COLUMN';
-- 5. Recreate constraints
ALTER TABLE Clients
ADD CONSTRAINT PK_Clients PRIMARY KEY (CNPJ_Client);
-- 6. Remove old column (after validation)
ALTER TABLE Clients DROP COLUMN CNPJ_Client_Old;
```
**Why this approach?**
- Avoids locking entire table
- Allows pausing/resuming migration
- Minimizes production impact
- Facilitates rollback if needed
---
### Phases 4 & 5: Execution and Validation
**Execution checklist:**
- [ ] Complete database backup
- [ ] Execute migration scripts in batches
- [ ] Update applications (validations, masks)
- [ ] Integration testing
- [ ] Referential integrity validation
- [ ] Performance testing
- [ ] Coordinated go-live
- [ ] 24h post-migration monitoring
---
## Sales Enablement: UX Presentation
**Collaboration with UX Manager:**
The company's UX manager created an **impactful visual presentation** of the CNPJ Fast process:
**Presentation content:**
- Infographics of the 5-phase process
- Examples of time/cost estimates
- Use cases (insurance, banks, fintechs)
- Executive checklist
- Documentation templates
**Result:** Presentation used by sales team for prospecting.
---
## Results & Impact
### Sales Achieved
**Client 1: Insurance Company**
- Stack: ASP 3.0, VB6 components, .NET, microservices
- Scope: Complete legacy application migration
- Status: **Project sold** (execution by another team)
- Value: [Confidential]
**Client 2: Collection Agency**
- Scope: Database migration (~100M records)
- Status: **Project sold and in execution** (by me)
- Particularity: Process was **restructured** to meet specific needs
- See complete case study: [CNPJ Migration - 100M Records](/cases/cnpj-migration-database)
---
### Business Impact
**2 projects sold** before first execution
**Replicable process** for new clients
**Positioning** as specialist in regulatory migrations
**Knowledge base** for future similar projects
---
### Technical Impact
**Tested methodology** in real scenarios
**Reusable documentation** (scripts, checklists, templates)
**Acceleration** of similar projects (from weeks to days)
---
## Tech Stack
`SQL Server` `Migration Strategy` `Process Design` `Regulatory Compliance` `ASP 3.0` `VB6` `.NET` `Microservices` `Batch Processing` `Database Optimization`
---
## Key Decisions & Trade-offs
### Why structured process?
**Alternatives:**
1. Ad-hoc approach per project
2. Manual consulting without methodology
3. **Replicable and scalable process**
**Justification:**
- Reduces Discovery time
- Standardizes deliveries
- Facilitates sales (ready presentation)
- Allows execution by different teams
### Why separate into 5 phases?
**Benefits:**
- Client can approve phase by phase
- Allows adjustments during process
- Facilitates risk management
- Incremental deliveries
---
## Lessons Learned
### 1. UX/Presentation Matters for Sales
The visual presentation made by the UX manager was **crucial** to closing both contracts. Good technical process + poor presentation = no sales.
### 2. Process Sells, Not Just Execution
Creating a **documented methodology** has more commercial value than just offering "consulting hours."
### 3. Each Client is Unique
The client requested **process restructuring**. A good process should be:
- Structured enough to be replicable
- Flexible enough to customize
### 4. Multidisciplinary Collaboration
Working with UX manager (presentations) + sales team (sales) + technical (execution) = success.
---
## Next Steps
**Future opportunities:**
1. **Expansion:** Offer CNPJ Fast to more sectors (banks, fintechs, retail)
2. **Product:** Transform into automated tool (SaaS)
3. **Training:** Enable clients' internal teams
4. **Evolution:** Adapt process for other regulatory migrations (PIX, Open Banking)
---
**Result:** Structured methodology that became a sellable product, generating revenue before the first technical execution.
[Want to implement CNPJ Fast in your company? Get in touch](#contact)

View File

@ -0,0 +1,469 @@
---
title: "Alphanumeric CNPJ Migration - 100 Million Records"
slug: "cnpj-migration-database"
summary: "Execution of massive CNPJ migration from numeric to alphanumeric in database with ~100M records, using phased commit strategy to avoid database locks."
client: "Collection Agency"
industry: "Collections & Financial Services"
timeline: "In execution"
role: "Database Architect & Tech Lead"
image: ""
tags:
- SQL Server
- Database Migration
- CNPJ
- Performance Optimization
- Batch Processing
- Big Data
featured: true
order: 4
date: 2024-11-01
seo_title: "Alphanumeric CNPJ Migration - 100M Records | Carneiro Tech"
seo_description: "Case study of massive CNPJ migration in database with 100 million records using phased commits and performance optimizations."
seo_keywords: "database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits"
---
## Overview
A collection agency that works with transitory data databases (no proprietary software) needs to adapt its systems to the new Brazilian **alphanumeric CNPJ** format.
**Main challenge:** Migrate ~**100 million records** in tables with `BIGINT` and `NUMERIC` columns to `VARCHAR`, without locking the production database.
**Status:** Project in execution (migration script preparation).
---
## Challenge
### Massive Data Volume
**Company context:**
- Collection agency (does not develop proprietary software)
- Works with **transitory data** (high turnover)
- SQL Server database with critical volume
**Initial analysis revealed:**
| Table | Column | Current Type | Records | Size |
|--------|--------|------------|-----------|---------|
| Debtors | CNPJ_Debtor | BIGINT | 8,000,000 | 60 GB |
| Transactions | CNPJ_Payer | NUMERIC(14) | 90,000,000 | 1.2 TB |
| Companies | CNPJ_Company | BIGINT | 2,500,000 | 18 GB |
| **TOTAL** | - | - | **~100,000,000** | **~1.3 TB** |
**Identified problems:**
1. **Tables with 8M+ rows** using `BIGINT` for CNPJ
2. **90 million records** in transactions table
3. **CNPJ as primary key** in some tables
4. **Foreign keys** relating multiple tables
5. **Impossibility of extended downtime** (24/7 operation)
6. **Disk space restrictions** (requires efficient strategy)
---
## Strategic Decision: Phased Commits
### Why NOT do ALTER COLUMN directly?
**Naive approach (DOESN'T work):**
```sql
-- NEVER DO THIS ON LARGE TABLES
ALTER TABLE Transactions
ALTER COLUMN CNPJ_Payer VARCHAR(18);
```
**Problems:**
- Locks entire table during conversion
- Can take hours/days on large tables
- Blocks all operations (INSERT, UPDATE, SELECT)
- Risk of timeout or failure mid-operation
- Complex rollback if something goes wrong
---
### Chosen Strategy: Column Swap with Phased Commits
**Based on previous experience**, I decided to use a gradual approach:
```
┌─────────────────────────────────────────────┐
│ 1. Create new VARCHAR column at END │
│ (fast operation, doesn't lock table) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 2. UPDATE in batches (phased commits) │
│ - 100k records at a time │
│ - Pause between batches (avoid lock) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 3. Remove PKs and FKs │
│ (after 100% migrated) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 4. Rename columns (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 5. Recreate PKs/FKs with new column │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 6. Validation and old column deletion │
└─────────────────────────────────────────────┘
```
**Why this approach?**
**No complete table lock** (incremental operation)
**Can pause/resume** at any time
**Real-time progress monitoring**
**Simple rollback** (just drop new column)
**Minimizes production impact** (small commits)
**Decision based on:**
- Previous experience with large volume migrations
- Knowledge of SQL Server locks
- Need for zero downtime
**Note:** This decision was made **without consulting AI** - based purely on practical experience from previous projects.
---
## Implementation Details
### Phase 1: Create New Column
```sql
-- Fast operation (metadata change only)
ALTER TABLE Transactions
ADD CNPJ_Payer_New VARCHAR(18) NULL;
-- Add temporary index to speed up lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transactions(CNPJ_Payer_New)
WHERE CNPJ_Payer_New IS NULL;
```
**Estimated time:** ~1 second (independent of table size)
---
### Phase 2: Batch Migration (Core Strategy)
```sql
-- Migration script with phased commits
DECLARE @BatchSize INT = 100000; -- 100k records per batch
DECLARE @RowsAffected INT = 1;
DECLARE @TotalProcessed INT = 0;
DECLARE @StartTime DATETIME = GETDATE();
WHILE @RowsAffected > 0
BEGIN
BEGIN TRANSACTION;
-- Update batch of 100k records not yet migrated
UPDATE TOP (@BatchSize) Transactions
SET CNPJ_Payer_New = RIGHT('00000000000000' + CAST(CNPJ_Payer AS VARCHAR), 14)
WHERE CNPJ_Payer_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
SET @TotalProcessed = @TotalProcessed + @RowsAffected;
COMMIT TRANSACTION;
-- Progress log
PRINT 'Processed: ' + CAST(@TotalProcessed AS VARCHAR) + ' rows. Batch: ' + CAST(@RowsAffected AS VARCHAR);
PRINT 'Elapsed time: ' + CAST(DATEDIFF(SECOND, @StartTime, GETDATE()) AS VARCHAR) + ' seconds';
-- Pause between batches (reduces contention)
WAITFOR DELAY '00:00:01'; -- 1 second between batches
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
```
**Configurable parameters:**
- `@BatchSize`: 100k (balanced between performance and lock time)
- Too small = many transactions, overhead
- Too large = prolonged lock, production impact
- `WAITFOR DELAY`: 1 second (gives time for other queries to run)
**Time estimates:**
| Records | Batch Size | Estimated Time |
|-----------|------------|----------------|
| 8,000,000 | 100,000 | ~2-3 hours |
| 90,000,000 | 100,000 | ~20-24 hours |
**Advantages:**
- Doesn't freeze application
- Other queries can run between batches
- Can pause (Ctrl+C) and resume later (WHERE NULL picks up where it left off)
- Real-time progress log
---
### Phase 3: Constraint Removal
```sql
-- Identifies all PKs and FKs involving the column
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transactions')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Payer';
-- Remove PKs
ALTER TABLE Transactions
DROP CONSTRAINT PK_Transactions_CNPJ;
-- Remove FKs (tables that reference)
ALTER TABLE Payments
DROP CONSTRAINT FK_Payments_Transactions;
```
**Estimated time:** ~10 minutes (depends on how many constraints exist)
---
### Phase 4: Column Swap (Renaming)
```sql
-- Rename old column to _Old
EXEC sp_rename 'Transactions.CNPJ_Payer', 'CNPJ_Payer_Old', 'COLUMN';
-- Rename new column to original name
EXEC sp_rename 'Transactions.CNPJ_Payer_New', 'CNPJ_Payer', 'COLUMN';
-- Change to NOT NULL (after validating 100% populated)
ALTER TABLE Transactions
ALTER COLUMN CNPJ_Payer VARCHAR(18) NOT NULL;
```
**Estimated time:** ~1 second (metadata change)
---
### Phase 5: Constraint Recreation
```sql
-- Recreate PK with new VARCHAR column
ALTER TABLE Transactions
ADD CONSTRAINT PK_Transactions_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Payer);
-- Recreate FKs
ALTER TABLE Payments
ADD CONSTRAINT FK_Payments_Transactions
FOREIGN KEY (CNPJ_Payer) REFERENCES Transactions(CNPJ_Payer);
```
**Estimated time:** ~30-60 minutes (depends on volume)
---
### Phase 6: Validation and Cleanup
```sql
-- Validate that 100% was migrated
SELECT COUNT(*)
FROM Transactions
WHERE CNPJ_Payer IS NULL OR CNPJ_Payer = '';
-- Validate referential integrity
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- If everything OK, remove old column
ALTER TABLE Transactions
DROP COLUMN CNPJ_Payer_Old;
-- Remove temporary index
DROP INDEX IX_Temp_CNPJ_New ON Transactions;
```
---
## CNPJ Fast Process Customization
### Differences vs. Original Process
The original **CNPJ Fast** process was **restructured** for this client:
**Main changes:**
| Aspect | Original CNPJ Fast | Client (Customized) |
|---------|-------------------|---------------------|
| **Focus** | Applications + DB | DB only (no proprietary software) |
| **Discovery** | App inventory | Schema analysis only |
| **Execution** | Multiple applications | Massive SQL scripts |
| **Batch Size** | 50k-100k | 100k (optimized for volume) |
| **Monitoring** | Manual + tools | Real-time SQL logs |
| **Rollback** | Complex process | Simple (DROP COLUMN) |
**Reason for restructuring:**
- Client has no proprietary applications (only consumes data)
- 100% focus on database optimization
- Much larger volume than typical cases (100M vs ~10M)
---
## Tech Stack
`SQL Server` `T-SQL` `Batch Processing` `Performance Tuning` `Database Optimization` `Migration Scripts` `Phased Commits` `Index Optimization` `Constraint Management`
---
## Key Decisions & Trade-offs
### Why 100k per batch?
**Performance tests:**
| Batch Size | Time/Batch | Lock Duration | Contention |
|------------|-------------|---------------|-----------|
| 10,000 | 2s | Low | Minimal |
| 50,000 | 8s | Medium | Acceptable |
| **100,000** | 15s | **Medium** | **Balanced** |
| 500,000 | 90s | High | Production impact |
| 1,000,000 | 180s | Very high | Unacceptable |
**Choice:** 100k offers best balance between performance and impact.
---
### Why create column at END?
**SQL Server internals:**
- Add column at end = metadata change (fast)
- Add in middle = page rewrite (slow)
- For large tables, position matters
---
### Why WAITFOR DELAY of 1 second?
**Without delay:**
- Batch processing consumes 100% of I/O
- Application queries slow down
- Lock escalation may occur
**With 1s delay:**
- Other queries have window to execute
- Distributed I/O
- User experience preserved
**Trade-off:** Migration takes +1s per batch (~25% slower), but system remains responsive.
---
## Current Status & Next Steps
### Current Status (December 2024)
**Preparation Phase:**
- Discovery complete (100M records identified)
- Migration scripts developed
- Tests in staging environment
- Performance validation in progress
- Awaiting production maintenance window
### Next Steps
1. **Complete production backup**
2. **Production execution** (24/7 environment)
3. **Real-time monitoring** during migration
4. **Post-migration validation** (integrity, performance)
5. **Lessons learned documentation**
---
## Lessons Learned (So Far)
### 1. Previous Experience is Gold
Decision to use phased commits came from **practical experience** in previous projects, not from documentation or AI.
**Similar previous situations:**
- E-commerce data migration (50M records)
- Encoding conversion (UTF-8 in 100M+ rows)
- Historical table partitioning
---
### 2. "Measure Twice, Cut Once"
Before executing in production:
- Exhaustive tests in staging
- Scripts validated and reviewed
- Rollback tested
- Time estimates confirmed
**Preparation time:** 3 weeks
**Execution time:** Estimated at 48 hours
**Ratio:** 10:1 (preparation vs execution)
---
### 3. Customization > One-Size-Fits-All
The original CNPJ Fast process needed to be **restructured** for this client.
**Lesson:** Processes should be:
- Structured enough to repeat
- Flexible enough to adapt
---
### 4. Monitoring is Crucial
Scripts with **detailed progress logs** allow:
- Estimate remaining time
- Identify bottlenecks
- Pause/resume with confidence
- Report status to stakeholders
```sql
-- Log example
Processed: 10,000,000 rows. Batch: 100,000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
```
---
## Performance Optimizations
### Optimizations Implemented
1. **Temporary index WHERE NULL**
- Speeds up lookup of unmigrated records
- Removed after completion
2. **Optimized batch size**
- Balanced between performance and lock time
3. **Transaction log management**
```sql
-- Check log growth
DBCC SQLPERF(LOGSPACE);
-- Adjust recovery model (if allowed)
ALTER DATABASE MyDatabase SET RECOVERY SIMPLE;
```
4. **Execution during low-load hours**
- Overnight maintenance window
- Weekend (if possible)
---
**Expected result:** Migration of 100 million records in ~48 hours, without significant downtime and with possibility of fast rollback.
[Need to migrate massive data volumes? Get in touch](#contact)

View File

@ -0,0 +1,588 @@
---
title: "Industrial Training Platform - From Wireframes to Complete System"
slug: "industrial-learning-platform"
summary: "Solution Design for microlearning platform in industrial gas company. Identification of critical unmapped requirements (admin, registrations, exports) before client presentation, avoiding rework and ensuring real usability."
client: "Industrial Gas Company"
industry: "Industrial & Manufacturing"
timeline: "4 months"
role: "Solution Architect & Tech Lead"
image: ""
tags:
- Solution Design
- EdTech
- Learning Platform
- Requirements Analysis
- Tech Lead
- User Stories
- .NET
- Product Design
featured: true
order: 5
date: 2024-06-01
seo_title: "Industrial Training Platform - Solution Design"
seo_description: "Case study of Solution Design for microlearning platform, identifying critical requirements before client presentation and leading development to production."
seo_keywords: "solution design, learning platform, microlearning, requirements analysis, tech lead, industrial training"
---
## Overview
Industrial gas company requests platform to train employees using **microlearning** methodology (short and objective content).
**Initial requirement:** "We just want the structure - track, microlearning, test question and score."
**Problem:** Incomplete specification that would result in a system **impossible to use** (no way to register content, no administrators, no export of results).
**Solution:** Critical requirements analysis **before client presentation**, identifying functional gaps and proposing complete solution.
---
## Challenge
### Beautiful Wireframes, Incomplete Functionality
**Initial situation:**
UX created beautiful wireframes showing:
- Learning tracks
- Microlearnings (video/text + image)
- Test questions (multiple choice)
- Score per employee
**Identified problem:**
Nobody (client, UX, commercial) thought about:
**How does content enter the system?**
- Who registers tracks?
- Who creates microlearnings?
- Who writes questions?
- Manual interface or import?
**Who manages the system?**
- Is there admin concept?
- Can HR create admins?
- Can area manager see only their team?
**How does data leave the system?**
- HR needs reports
- Compliance needs evidence
- How to export data?
- Format: Excel? PDF? API?
**Real risk:**
If we developed exactly what was requested:
- System would work technically
- **But would be completely unusable**
- Client would have to pay for rework to include basic CRUD
- Rework + additional cost + frustration
---
## Solution Design Process
### Step 1: Critical Analysis (Before Presentation)
**Action taken:** Called meeting with UX **before** presenting to client.
**Points raised:**
**"How does the first content enter the system?"**
- UX: "Ah... we didn't think about that. Will you populate the database?"
- Me: "And when client wants to add new track? Will we modify production database?"
**"Who is the system owner?"**
- UX: "HR, I imagine."
- Me: "Just one person? What if they leave the company? How do they delegate?"
**"Did HR ask for reports?"**
- UX: "It wasn't mentioned in the briefing."
- Me: "HR always needs reports. It's for compliance (NR, ISO)."
---
### Step 2: Identified Functional Requirements
I proposed 4 additional **essential** modules:
#### 1. Administration System
**Features:**
- Standard user: Only takes training
- Admin user: Manages content + sees reports
- Admin can promote other users to admin
- Access control (general admin vs area admin)
**Why it's critical:**
System is static without this (content never updates).
---
#### 2. Content CRUD
**a) Track Registration:**
- Track name
- Description
- Microlearning order
- Active/inactive track (allows unpublishing)
**b) Microlearning Registration:**
- Title
- Type: Simple text (2 paragraphs) OR Video
- Image upload (if text)
- Video URL (if video)
- Order within track
**c) Question Registration:**
- Question (text)
- 3 answer options:
- "Great" (green)
- "So-so" (yellow)
- "Poor" (red)
- Points per answer (e.g., 10, 5, 0 points)
- Custom feedback per answer
**Why it's critical:**
Client needs to update content without calling dev/DBA.
---
#### 3. Data Export
**Features:**
- Export to Excel (.xlsx)
- Filters:
- By period (start/end date)
- By track
- By employee
- By area/department
- Exported columns:
- Employee name
- ID number
- Completed track
- Total score
- Completion date
- Individual answers (for audit)
**Why it's critical:**
HR needs to evidence training for:
- Regulatory Norms (NR-13, NR-20 - flammable gases)
- ISO audits
- Labor lawsuits
---
#### 4. User Management
**Features:**
- Import employees (CSV/Excel upload)
- Manual registration
- Activate/deactivate users
- Assign mandatory tracks by area
- Pending notifications
**Why it's critical:**
Company has 500+ employees, manual registration is unfeasible.
---
### Step 3: Client Presentation
**Approach:**
1. Showed UX wireframes (beautiful interface)
2. Asked: "How will you register the first track?"
3. Client: "Ah... good question. We hadn't thought about that."
4. Presented the 4 additional modules
5. Client: "Makes total sense! Without this we can't use it."
**Result:**
- Proposal approved **with additional modules**
- Adjusted scope (timeline + budget)
- Zero future rework
- Client recognized added value
---
## Implementation
### My Role in the Project
**1. Solution Architect**
- Identification of non-functional requirements
- Architecture design (modules, integrations)
- Technology definition
**2. Tech Lead**
- Technical team leadership (3 devs)
- Code review
- Code standards definition
- Technical risk management
**3. Technical Product Owner**
- Creation of complete **user stories**
- Backlog prioritization
- Continuous refinement with client
---
### Chosen Tech Stack
**Backend:**
- `.NET 7` - REST APIs
- `Entity Framework Core` - ORM
- `SQL Server` - Database
- `ClosedXML` - Excel generation
**Frontend:**
- `React` - Web interface
- `Material-UI` - Components
- `React Player` - Video player
- `Chart.js` - Progress charts
**Infrastructure:**
- `Azure App Service` - Hosting
- `Azure Blob Storage` - Video/image storage
- `Azure SQL Database` - Managed database
---
### Created User Stories
I wrote **32 user stories** covering all flows. Examples:
**US-01: Register Track (Admin)**
```
As system administrator
I want to register a new training track
So that employees can take the courses
Acceptance criteria:
- Admin accesses "Tracks" menu → "New Track"
- Fills in: Name, Description, Status (Active/Inactive)
- Can add existing microlearnings to track
- Defines microlearning order (drag & drop)
- System validates mandatory fields
- Saves and displays success message
```
**US-15: Complete Microlearning (Employee)**
```
As employee
I want to complete a microlearning from my track
To learn about the topic and earn points
Acceptance criteria:
- Employee accesses assigned track
- Sees list of microlearnings (uncompleted first)
- Clicks microlearning → opens screen with:
- Text (2 paragraphs) + Image OR
- Embedded video player
- "Continue" button appears after:
- 30s (if text)
- End of video (if video)
- Marks microlearning as seen
- Test question appears automatically
```
**US-22: Export Results (Admin)**
```
As administrator
I want to export training results to Excel
To generate compliance and audit reports
Acceptance criteria:
- Admin accesses "Reports" → "Export"
- Selects filters (period, track, area)
- Clicks "Generate Excel"
- System processes and downloads .xlsx file
- Excel contains columns: Name, ID, Track, Points, Date, Answers
- Readable format (bold headers, auto-adjusted columns)
```
---
## Key Features Implemented
### 1. Gamified Scoring System
**Mechanics:**
- Each question worth points (configurable)
- "Great" answer: 10 points
- "So-so" answer: 5 points
- "Poor" answer: 0 points
**Employee dashboard:**
- Total score
- Ranking (optional, configurable)
- Badges for completed tracks
- Visual progress (% bar)
**Why it works:**
Factory floor employees engage more with gamification elements.
---
### 2. Adaptive Microlearning
**Content types:**
**Text + Image:**
- 2 paragraphs (max 300 words)
- 1 illustrative image
- Ideal for: Procedures, norms, concepts
**Video:**
- Short videos (2-5 min)
- Embedded player (YouTube/Vimeo or upload)
- Ideal for: Demonstrations, equipment operations
**Why microlearning?**
- Employees complete during breaks (10-15min)
- Short content = higher retention
- Facilitates updates (vs long courses)
---
### 3. Delegated Administration System
**Hierarchy:**
```
General Admin (HR)
↓ can promote
Area Admin (Managers)
↓ can view only
Employees from their area
```
**Permissions:**
- General admin: Creates tracks, promotes admins, sees all data
- Area admin: Sees only their area reports
- Employee: Only takes training
**Audit:**
- Logs of who created/edited each content
- History of admin promotions
- SOX/ISO compliance
---
### 4. Export for Compliance
**Generated Excel format:**
| ID | Name | Area | Track | Completion Date | Points | Status |
|-----------|------|------|--------|----------------|--------|--------|
| 1001 | John Silva | Production | NR-20 Safety | 11/15/2024 | 95/100 | Approved |
| 1002 | Mary Santos | Logistics | Gas Handling | 11/14/2024 | 78/100 | Approved |
**Additional sheet: Answer Details**
- Allows audit: "Did employee X answer question Y correctly?"
- Evidence for labor lawsuits
- NR-13/NR-20 compliance
---
## Results & Impact
### System in Production
**Current status:** In use for 4+ months
**Adoption metrics:**
- 500+ registered employees
- 12 active tracks
- 150+ created microlearnings
- 8,000+ completed training sessions
- 100+ exported reports (compliance)
**Completion rate:** 87% (industry average: 45%)
---
### Client Impact
**Before:**
- In-person training (high cost, difficult scheduling)
- Paper evidence (losses, difficult audit)
- Difficulty updating content
**After:**
- Asynchronous training (employee completes when possible)
- Digital evidence (facilitated compliance)
- HR updates content without calling IT
- 70% reduction in training cost
**Client feedback:**
> "If we had implemented only what we initially requested, the system would be useless. The pre-analysis saved the project."
---
### Solution Design Value
**ROI of pre-sale analysis:**
**Scenario A (without analysis):**
1. Develop interface only (2 months)
2. Client tests and realizes CRUD is missing (1 month later)
3. Rework to add modules (2+ months)
4. **Total: 5+ months + client frustration**
**Scenario B (with analysis - what we did):**
1. Identify requirements beforehand (1 week)
2. Approve complete scope (1 week)
3. Develop correct solution (4 months)
4. **Total: 4 months + satisfied client**
**Savings:** 1+ month of rework + opportunity cost
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React` `Material-UI` `Azure App Service` `Azure Blob Storage` `ClosedXML` `Chart.js` `User Stories` `Solution Design` `Tech Lead`
---
## Key Decisions & Trade-offs
### Why not use ready-made LMS? (Moodle, Canvas)
**Alternatives considered:**
1. Moodle (open-source, free)
2. Totara/Canvas (corporate LMS)
3. **Custom development**
**Justification:**
- Generic LMS: Unnecessary complexity (forums, wikis, etc)
- Client wants **only microlearning** (simplicity)
- LMS license cost > custom dev cost
- Client AD/SSO integration (easier custom)
- UX optimized for factory floor (mobile-first, touch)
---
### Why 3 answer options (vs 4-5)?
**Choice:** Green (Great), Yellow (So-so), Red (Poor)
**Justification:**
- Factory floor employees prefer simplicity
- Universal colors (traffic light)
- Avoids choice paradox (fewer options = more engagement)
- Clearer gamification
---
### Why Excel Export (vs Online Dashboard)?
**Both were implemented**, but Excel is critical for:
**Regulatory compliance:**
- Auditors ask for "digitally signed file"
- NR-13/NR-20 require physical evidence
- Labor lawsuits accept Excel
**Flexibility:**
- HR can do custom analyses in Excel
- Combine with other data sources
- Presentations for board
---
## Lessons Learned
### 1. Solution Design Prevents Rework
**Lesson:** 1 week of critical analysis saves months of rework.
**Application:**
- Always question incomplete specifications
- Think about "the day after" (who manages this in production?)
- Involve client in requirements discussions
---
### 2. UX ≠ Functional Requirements
**Lesson:** Beautiful wireframes don't replace requirements analysis.
**UX focuses on:** How user **uses** the system
**Solution Design focuses on:** How system **works** end-to-end
Both are necessary and complementary.
---
### 3. Asking "How?" is More Important than "What?"
**Client says:** "I want tracks and microlearnings"
**Solution Designer asks:** "How does the first track enter the system?"
This simple question revealed 4 missing modules.
---
### 4. Well-Written User Stories Accelerate Development
**Investment:** 2 weeks writing 32 detailed user stories
**Return:**
- Devs knew exactly what to build
- Zero ambiguity
- Very few bugs (clear requirements)
- Client validated stories before coding
**Lesson:** Time spent planning reduces development time.
---
### 5. Compliance is Hidden Requirement
**In regulated industries** (health, energy, chemical), there will always be:
- Audit needs
- Evidence exports
- Logs of who did what
**Lesson:** Ask about compliance **before**, not after.
---
## Challenges Overcome
| Challenge | Solution | Result |
|---------|---------|-----------|
| Incomplete specification | Pre-sale critical analysis | Correct scope from start |
| Client without technical knowledge | User stories in business language | Client validated requirements |
| Employees with low digital literacy | Simplified UX (3 buttons, colors) | 87% completion rate |
| NR-13/NR-20 compliance | Excel export with details | Approved in 2 audits |
| Managing 500+ users | CSV import + admin hierarchy | Onboarding in 1 week |
---
## Next Steps (Future Roadmap)
**Planned features:**
1. **Push Notifications**
- Remind employee of pending training
- Notify of new mandatory track
2. **Native Mobile App**
- Offline-first (downloaded videos)
- Employees without computer
3. **Digital Certificates**
- Digitally signed PDF
- QR code for validation
4. **Data Intelligence**
- Which microlearnings have most errors?
- Identify knowledge gaps by area
---
**Result:** Functional system in production, satisfied client, zero rework - all because 1 week was invested in **thinking before coding**.
[Need critical requirements analysis? Get in touch](#contact)

View File

@ -0,0 +1,577 @@
---
title: "Pharma Lab Digital MVP - From Zero to Production"
slug: "pharma-digital-transformation"
summary: "Squad leadership in greenfield project for pharmaceutical lab, building digital platform MVP with complex integrations (Salesforce, Twilio, official APIs) starting from absolute zero - no Git, no servers, no infrastructure."
client: "Pharmaceutical Laboratory"
industry: "Pharmaceutical & Healthcare"
timeline: "4 months (2-month planned delay)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- MVP
- Digital Transformation
- .NET
- React
- Next.js
- Salesforce
- Twilio
- SQL Server
- Tech Lead
- Greenfield
featured: true
order: 3
date: 2023-03-01
seo_title: "Pharma Digital MVP - Digital Transformation from Scratch"
seo_description: "Case study of building digital MVP for pharmaceutical lab from scratch: no Git, no infrastructure, with complex integrations and successful delivery."
seo_keywords: "MVP, digital transformation, pharma, .NET, React, Next.js, Salesforce, greenfield project, tech lead"
---
## Overview
Pharmaceutical laboratory at the **beginning of digital transformation** hires consulting firm to build discount platform for prescribing physicians, starting from WordPress prototype.
**Unique challenge:** Start greenfield project in company **without basic development infrastructure** - no Git, no provisioned servers, no defined processes.
**Context:** Project executed in multi-squad environment. **Successful production delivery** despite initial infrastructure challenges, with controlled 2-month delay.
---
## Challenge
### Digital Transformation... Starting from Absolute Zero
**Company initial state (2023):**
**No Git/versioning**
- Code only on local machines
- Non-existent history
- Impossible collaboration
**No provisioned servers**
- Non-existent development environment
- Staging not configured
- Production not prepared
**No development processes**
- No CI/CD
- No code review
- No structured task management
**No experienced internal technical team**
- Team unfamiliar with modern stacks
- First contact with React, REST APIs
- Inexperience with complex integrations
**Technical starting point:**
- Functional prototype in **WordPress**
- Content and texts already approved
- UX/UI defined
- Business rules documented (partially)
---
### Required Complex Integrations
The MVP needed to integrate with multiple external systems:
1. **Salesforce** - Discount order registration
2. **Twilio** - SMS for login validation (2FA)
3. **Official physician API** - CRM validation + professional data
4. **Interplayers** - Discount record sending by CPF
5. **WordPress** - Content reading (headless CMS)
6. **SQL Server** - Data persistence
**Additional complexity:**
- Different credentials/environments per integration
- Varying SLAs (Twilio critical, WordPress tolerant)
- Provider-specific error handling
- LGPD compliance (sensitive physician data)
---
## Solution Architecture
### Strategy: Start Small, Build Solid
**Initial decision:** Explain to the team the process we would follow, establishing foundations before coding.
### Phase 1: Basic Infrastructure Setup (Weeks 1-2)
Even without provisioned servers, I started essential setup:
**Git & Versioning:**
```bash
# Structured repository from day 1
git init
git flow init # Defined branch strategy
# Monorepo structure
/
├── frontend/ # Next.js + React
├── backend/ # .NET APIs
├── cms-adapter/ # WordPress integration
└── docs/ # Architecture and ADRs
```
**Process explained to team:**
1. Everything in Git (atomic commits, descriptive messages)
2. Feature branches (never commit directly to main)
3. Mandatory code review (2 approvals)
4. CI/CD prepared (for when servers are ready)
**Local environments first:**
- Docker Compose for local development
- External API mocks (until credentials arrive)
- Local SQL Server with data seeds
---
### Phase 2: Modern & Decoupled Architecture
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND (Next.js + React) │
│ - SSR for SEO │
│ - Client-side for interactivity │
│ - API consumption │
└────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ BACKEND APIs (.NET 7) │
│ - REST APIs │
│ - Authentication/Authorization │
│ - Business logic │
│ - Orchestration layer │
└────┬────┬────┬────┬────┬─────────────────────────┬──┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│Salesf. │ │Twilio│ │CRM │ │Interpl.│ │WordPr. │ │SQL Server│
│ │ │ │ │API │ │ │ │(CMS) │ │ │
└────────┘ └──────┘ └──────┘ └────────┘ └────────┘ └──────────┘
```
**Chosen stack:**
**Frontend:**
- `Next.js 13` - SSR, routing, optimizations
- `React 18` - Components, hooks, context
- `TypeScript` - Type safety
- `Tailwind CSS` - Modern styling
**Backend:**
- `.NET 7` - REST APIs
- `Entity Framework Core` - ORM
- `SQL Server 2019` - Database
- `Polly` - Resilience patterns (retry, circuit breaker)
**Why Next.js instead of keeping WordPress?**
- Performance (SSR vs monolithic PHP)
- Optimized SEO (critical for pharma)
- Modern experience (SPA when needed)
- Scalability
- WordPress kept only as CMS (headless)
---
### Phase 3: Integrations (Project Core)
#### 1. Salesforce - Campaigns and Order Registration
**Implemented solution:**
Salesforce was configured to manage two main functionalities:
**a) Discount campaigns:**
- Marketing configures campaigns in Salesforce (medication X, discount Y%, period)
- Backend queries active campaigns via API
- Frontend (Next.js) displays available discount percentage based on: medication + active campaign
**b) Order registration:**
- User informs: physician CRM, state, patient CPF, medication
- System validates data (real CRM via official API, valid CPF)
- Percentage calculated automatically (Salesforce campaigns + CMS rules)
- Order registered in Salesforce with all data (LGPD compliance)
**Overcome technical challenges:**
- OAuth2 authentication with automatic refresh token
- Rate limiting (Salesforce has API/day limits)
- Retry logic for transient failures (Polly)
- CPF masking for logs (LGPD)
---
#### 2. Twilio - SMS Authentication (2FA)
**Implemented solution:**
Two-factor authentication system to ensure security:
**Login flow:**
1. User enters phone number
2. Backend generates 6-digit code (valid for 5 minutes)
3. SMS sent via Twilio ("Your code: 123456")
4. User enters code in frontend
5. Backend validates code + expiration timestamp
6. JWT token issued after successful validation
**Compliance and audit:**
- Phone numbers masked in logs (LGPD)
- Complete audit (who, when, which SMS)
- Delivery rate: 99.8%
---
#### 3. Official Physician API (Regional Medical Council)
**Implemented solution:**
Automatic physician validation via official medical council API:
**Performed validations:**
- CRM exists and is active in council
- Physician name matches informed CRM
- Specialty is allowed (lab business rule)
- State corresponds to registration state
**Optimizations:**
- 24-hour cache to reduce official API calls
- Fallback if API is down (notifies admin)
- Automatic retry for transient failures
**Why this matters:**
Ensures only real and active physicians can prescribe discounts, avoiding fraud.
---
#### 4. WordPress as Headless CMS
**Implemented solution:**
Marketing continues managing content in WordPress (familiar), but frontend is modern Next.js.
**Architecture:**
- WordPress: Manages texts, images, campaign rules
- WordPress REST API: Exposes content via JSON
- Next.js: Consumes API and renders with SSR (SEO optimized)
**Benefits:**
- Marketing doesn't need to learn new tool
- Modern frontend (performance, UX)
- Optimized SEO (Server-Side Rendering)
- Clear separation of responsibilities (content vs code)
---
### Phase 4: Resilience & Error Handling
With multiple external integrations, failures are inevitable. The solution was to implement **resilience patterns** using Polly library (.NET):
**Implemented patterns:**
**1. Retry**
- If Salesforce/Twilio/CRM API fail, system automatically retries 2-3x
- Wait grows exponentially (1s, 2s, 4s) to avoid overload
- Only transient errors (timeout, 503) are retried
**2. Circuit Breaker**
- If service fails 5x in a row, "opens circuit" for 30s
- During 30s, doesn't try anymore (avoids wasting resources)
- After 30s, tries again (may have recovered)
**3. Timeout**
- Each integration has maximum response time
- Avoids indefinitely stuck requests
**4. Fallback (Plan B)**
- Salesforce down: Order goes to queue, processes later
- Twilio down: Alert administrator via email
- CRM API down: Uses cache (24h old data)
- WordPress down: Displays pre-loaded static content
**Strategies per integration:**
| Integration | Retry | Circuit Breaker | Timeout | Plan B |
|----------|-------|-----------------|---------|----------|
| Salesforce | 3x (exponential) | 5 failures/30s | 10s | Retry queue |
| Twilio | 2x (linear) | 3 failures/60s | 5s | Admin alert |
| CRM API | 3x (exponential) | No | 15s | Cache |
| WordPress | No | No | 3s | Static content |
**Production result:**
- Salesforce had maintenance (1h) → System continued working (queue processed later)
- Twilio had instability → Automatic retry resolved 95% of cases
- Zero downtime perceived by users
---
## Overcoming Infrastructure Challenges
### Problem: Servers Not Provisioned
**Temporary solution:**
1. 100% local development (Docker Compose)
2. External service mocks (when credentials delayed)
3. CI/CD prepared but not active (awaiting infra)
**When servers arrived (week 6):**
- Deploy in 2 hours (already prepared)
- Zero surprises (everything tested locally)
- Smooth rollout
---
### Problem: Delayed Integration Credentials
**Impact:** Twilio and Salesforce took 3 weeks to be provisioned.
**Solution:** Create "mock" (simulated) versions of each integration:
- Twilio mock: Logs instead of sending real SMS
- Salesforce mock: Saves order to local JSON file
- CRM API mock: Returns fictional physician data
**How it works:**
- Development environment: Uses mocks (no credentials needed)
- Production environment: Uses real integrations (when credentials arrive)
- Automatic switch based on configuration
**Result:** Team stayed 100% productive for 3 weeks, testing complete flows without depending on credentials.
---
### Problem: Team Inexperienced with Modern Stack
**Context:** Team had no experience with React, TypeScript, modern .NET Core, REST APIs.
**Enablement approach:**
**1. Pair Programming (1h/day per developer)**
- Tech lead works alongside dev
- Screen sharing + real-time explanation
- Dev writes code, tech lead guides
**2. Educational Code Review**
- Not just "approve" or "reject"
- Comments explain the **why** of each suggestion
- Example: "Always handle request errors! If API crashes, user needs to know what happened."
**3. Living Documentation**
- ADRs (Architecture Decision Records): Why did we choose X and not Y?
- READMEs: How to run, test, deploy
- Onboarding guide: From zero to first feature
**4. Weekly Live Coding (2h)**
- Tech lead solves real problem live
- Team observes thinking process
- Q&A at end
**Result:**
- After 4 weeks, team was autonomous
- Code quality consistently increased
- Devs started doing code review among themselves (peer review)
---
## Results & Impact
### Successful Delivery Despite Challenges
**Context:** Program with multiple squads working in parallel.
**Achieved result:**
- **MVP delivered to production successfully**
- Controlled 2-month delay (significantly less than other program initiatives)
- All integrations working as planned
- Zero critical bugs in production (first week)
**Why was delivery successful?**
1. **Anticipated setup** - Git, processes, local Docker from day 1
2. **Strategic mocks** - Team wasn't blocked waiting for infra
3. **Solid architecture** - Resilience from the start
4. **Continuous upskilling** - Team learned by doing
5. **Proactive communication** - Risks reported early
---
### MVP Metrics
**Performance:**
- Loading time: <2s (95th percentile)
- Lighthouse score: 95+ (mobile)
- SSL A+ rating
**Integrations:**
- Salesforce: 100% orders synchronized
- Twilio: 99.8% delivery rate
- CRM API: 10k validations/day (average)
- SQL Server: 50k records/month
**Adoption:**
- 2,000+ registered physicians (first 3 months)
- 15,000+ processed discount orders
- 4.8/5 satisfaction (internal survey)
---
### Client Impact
**Digital transformation initiated:**
- Git implemented and adopted
- Established development processes
- Internal team enabled in modern stacks
- Cloud infrastructure configured (Azure)
- Evolution roadmap defined
**Foundation for future projects:**
- Architecture served as reference for other initiatives
- Documented code patterns (coding standards)
- Reused CI/CD pipelines
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React 18` `Next.js 13` `TypeScript` `Tailwind CSS` `Salesforce API` `Twilio` `WordPress REST API` `Docker` `Polly` `OAuth2` `JWT` `LGPD Compliance`
---
## Key Decisions & Trade-offs
### Why Next.js instead of pure React?
**Requirements:**
- Critical SEO (pharma needs to rank)
- Performance (physicians use mobile)
- Dynamic content (WordPress)
**Next.js offers:**
- SSR out-of-the-box
- API routes (BFF pattern)
- Automatic optimizations (image, fonts)
- Simplified deploy (Vercel, Azure)
---
### Why keep WordPress?
**Alternatives considered:**
1. Migrate content to database + custom CMS (time)
2. Strapi/Contentful (costs + learning curve)
3. **WordPress headless** (best trade-off)
**Advantages:**
- Marketing team already knows how to use
- Approved content was already there
- WordPress REST API is solid
- Zero cost (already running)
---
### Why .NET 7 instead of Node.js?
**Context:** Client had preference for Microsoft stack.
**Additional benefits:**
- Superior performance (vs Node in APIs)
- Native type safety (C#)
- Entity Framework (mature ORM)
- Easy Azure integration (future deploy)
- Client team had familiarity
---
## Lessons Learned
### 1. Infrastructure Delayed? Prepare Alternatives
Don't wait for servers/credentials to start:
- Local Docker is your friend
- Mocks allow progress
- CI/CD can be prepared before having where to deploy
**Lesson:** Control what you can control.
---
### 2. Processes > Tools
Even without corporate Git, I established:
- Branching strategy
- Code review
- Commit conventions
- Documentation standards
**Result:** When tools arrived, team already knew how to use them.
---
### 3. Upskilling is Investment, Not Cost
Pair programming and code reviews took time, but:
- Team became autonomous faster
- Code quality increased
- Natural knowledge sharing
- Simplified onboarding of new devs
---
### 4. Resilience from the Start
Implementing Polly (retry, circuit breaker) at the start saved in production:
- Twilio had instability (resolved automatically)
- Salesforce had maintenance (queue worked)
- CRM API had slowness (cache mitigated)
**Lesson:** Don't leave resilience for "later". Failures will happen.
---
### 5. Clear Risk Communication
I reported weekly:
- Blockers (infrastructure, credentials)
- Risks (deadlines, dependencies)
- Alternative solutions (mocks, workarounds)
**Result:** Stakeholders knew exact status and had no surprises.
---
## Challenges & How They Were Overcome
| Challenge | Impact | Solution | Result |
|---------|---------|---------|-----------|
| No Git | Total blocker | Local setup + GitLab Cloud | Team productive day 1 |
| No servers | No dev environment | Local Docker Compose | Complete local dev/test |
| Delayed credentials | Integration blocked | Mock services | Progress without blocker |
| Inexperienced team | Low quality code | Pair prog + Code review | Ramp-up in 4 weeks |
| Multiple integrations | High complexity | Polly + patterns | Zero prod downtime |
---
## Next Steps (Post-MVP)
**Roadmap suggested to client:**
1. **Phase 2: Feature expansion**
- Dashboard for physicians (order history)
- Push notifications (Firebase)
- E-commerce integration (direct purchase)
2. **Phase 3: Optimizations**
- Distributed cache (Redis)
- CDN for static assets
- Advanced analytics (Amplitude)
3. **Phase 4: Scale**
- Kubernetes (AKS)
- Microservices (break monolith)
- Event-driven architecture (Azure Service Bus)
---
**Result:** MVP delivered to production despite starting literally from zero, establishing solid foundations for client's digital transformation.
[Need to build an MVP in a challenging scenario? Get in touch](#contact)

View File

@ -0,0 +1,211 @@
---
title: "SAP Healthcare Integration System"
slug: "sap-integration-healthcare"
summary: "Bidirectional integration processing 100k+ transactions/day with 99.9% uptime"
client: "Confidential - Healthcare Multinational"
industry: "Healthcare"
timeline: "6 months"
role: "Integration Architect"
image: ""
tags:
- SAP
- C#
- .NET
- Integrations
- Enterprise
- Healthcare
featured: true
order: 1
date: 2023-06-15
seo_title: "Case Study: SAP Healthcare Integration - 100k Transactions/Day"
seo_description: "How we architected SAP integration system processing 100k+ daily transactions with 99.9% uptime for healthcare company."
seo_keywords: "SAP integration, C#, .NET, SAP Connector, enterprise integration, healthcare"
---
## Overview
**Client:** Healthcare Multinational (confidential)
**Size:** 100,000+ employees
**Project:** Benefits integration
**Timeline:** 6 months
**My Role:** Integration Architect
---
## Challenge
Client had internal benefits management system that needed to sync with SAP ECC to process payroll.
### Main pain points:
- Manual process prone to errors
- 3-5 day delay between systems
- 100k employees waiting for processing
- Load spikes (month-end)
### Constraints:
- Limited budget (no SAP BTP)
- Small internal SAP team (2 developers)
- Tight deadline (6-month go-live)
- Legacy .NET Framework 4.5 system
---
## Solution
Bidirectional integration architecture:
```
[Internal System] ←→ [Queue] ←→ [SAP Connector] ←→ [SAP ECC]
↓ ↓
[MongoDB Logs] [ABAP Z_BENEFITS]
```
### Components:
- .NET Service with SAP Connector (NCo 3.0)
- Custom ABAP transaction (Z_BENEFITS)
- Queue system (RabbitMQ) for retry logic
- MongoDB for audit and troubleshooting
- Scheduler (Hangfire) for batch processing
### Flow:
1. System generates changes (new hires, modifications)
2. Service processes batch (500 records/batch)
3. SAP Connector calls Z_BENEFITS via RFC
4. SAP returns status (success/error)
5. Automatic retry if failure (max 3x)
6. MongoDB logs for troubleshooting
---
## Results
### Metrics:
- **100k+** transactions/day processed
- **99.9%** uptime
- Reduced **5 days → 4 hours** (delay)
- **80%** reduction in processing time
- **Zero** manual errors (vs 2-3% before)
### Benefits:
- Employees receive benefits on-time
- HR team saves 40h/month (manual work)
- Complete audit (compliance)
- Scalable (30% year-over-year growth without refactor)
---
## Tech Stack
`C#` `.NET Framework 4.5` `SAP NCo 3.0` `RabbitMQ` `MongoDB` `Hangfire` `Docker` `SAP ECC` `ABAP` `RFC`
---
## Decisions & Motivation
### Decision 1: SAP Connector vs SAP BTP
**Options evaluated:**
- SAP BTP (events, modern APIs, cloud)
- SAP Connector (direct RFC, on-premise)
**We chose:** SAP Connector
**Motivation:**
- Client had on-premise SAP ECC (not S/4)
- Budget didn't allow BTP license
- SAP team comfortable with ABAP/RFC
- Needs met with RFC (didn't need real-time event-driven)
**Accepted trade-off:**
- Less "modern" than BTP, but 100% functional
- $0 additional cost vs $30k+/year BTP
- 2 months faster delivery (no BTP learning curve)
---
### Decision 2: Queue System vs Direct Calls
**Options evaluated:**
- Direct synchronous calls (simpler)
- Queue with retry (more complex)
**We chose:** Queue + Retry
**Motivation:**
- SAP occasionally unavailable (maintenance)
- Load spikes (month-end = 200k requests)
- Ensure zero data loss
- Resilience > simplicity (critical environment)
**Implementation:**
- RabbitMQ with dead-letter queue
- Exponential retry (1min, 5min, 15min)
- Alerts if 3 consecutive failures
**Result:**
- Zero data loss in 2 years production
- HR team doesn't need to "keep watch"
---
### Decision 3: Custom ABAP vs Standard
**Options evaluated:**
- Standard SAP BAPIs (zero ABAP code)
- Custom transaction (Z_BENEFITS)
**We chose:** Custom transaction
**Motivation:**
- Standard BAPIs didn't have business-specific validations
- Client wanted logic centralized in SAP (single source of truth)
- Allowed complex validations (eligibility, dependents, limits)
**Trade-off:**
- Requires ABAP maintenance (internal SAP team)
- But: Client preferred vs duplicate logic (risk of desync)
---
### Alternatives NOT Chosen
**Webhook/Callback (Event-Driven):**
- Client had no infrastructure to expose APIs
- Internal system behind firewall
- Batch polling works well for the case
**Kubernetes Microservices:**
- Overkill for single integration
- Team had no K8s expertise
- Simple Docker sufficient
**Real-time Sync (<1min):**
- Business doesn't need (daily batch ok)
- Infrastructure cost would increase 3x
- 4h delay acceptable for payroll
---
## Learnings
### What worked very well:
- Involve SAP team from day 1 (buy-in)
- MongoDB for logs (10x faster troubleshooting)
- Retry logic saved countless times
### What I would do differently:
- Add health check endpoint earlier
- Monitoring dashboard from start (added later)
### Lessons for next projects:
- Client "limited budget" ≠ "limited solution" - creativity solves
- Document ALL architectural decisions (team turnover)
- Simplicity beats complexity when both work (KISS)
---
## Need Something Similar?
Complex SAP integrations, legacy systems, or high-availability architecture?
[Let's talk about your challenge →](/#contact)

View File

@ -0,0 +1,329 @@
---
title: "Migración ASP 3.0 a .NET Core - Sistema de Rastreo de Cargas"
slug: "asp-to-dotnet-migration"
summary: "Tech Lead en la migración gradual de sistema crítico ASP 3.0 a .NET Core, con sincronización de datos entre versiones y reducción de costos de $20k/año en APIs de mapeo."
client: "Empresa de Logística y Rastreo"
industry: "Logística & Seguridad"
timeline: "12 meses (migración completa)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- ASP Classic
- .NET Core
- SQL Server
- Migration
- Tech Lead
- OSRM
- APIs
- Arquitectura
featured: true
order: 2
date: 2015-06-01
seo_title: "Migración ASP 3.0 a .NET Core - Case Carneiro Tech"
seo_description: "Caso de migración gradual de aplicación ASP 3.0 a .NET Core con sincronización de datos y reducción de $20k/año en costos de APIs."
seo_keywords: "ASP migration, .NET Core, legacy modernization, SQL Server, OSRM, tech lead, routing API"
---
## Descripción General
Sistema crítico de monitoreo de cargas de alto valor (TVs LED de $600 cada una, cargamentos de hasta 1000 unidades) utilizando rastreo GPS vía satélite. La aplicación cubría todo el ciclo: desde registro y evaluación de conductores (verificación de antecedentes policiales) hasta monitoreo en tiempo real y entrega final.
**Desafío principal:** Migrar aplicación legacy ASP 3.0 a .NET Core sin downtime, manteniendo operación crítica 24/7.
---
## Desafío
### Sistema Legacy Crítico
La empresa operaba un sistema mission-critical en **ASP 3.0** (Classic ASP) que no podía detenerse:
**Tecnología legacy:**
- ASP 3.0 (tecnología de 1998)
- SQL Server 2005
- Cluster failover on-premises (perfectamente capaz de soportar la carga)
- Integración con rastreadores GPS vía satélite
- Google Maps API (costo: **$20,000/año** solo para cálculo de rutas)
**Restricciones:**
- Sistema operando 24/7 con cargas de alto valor
- Imposibilidad de downtime durante migración
- Múltiples módulos interdependientes
- Equipo necesitaba continuar desarrollando features durante la migración
---
## Arquitectura de Solución
### Fase 1: Preparación de Infraestructura (Meses 1-3)
#### Upgrade de Base de Datos
```
SQL Server 2005 → SQL Server 2014
- Backup completo y validación
- Migración de stored procedures
- Optimización de índices
- Pruebas de performance
```
#### Estrategia de Sincronización Dual-Write
Implementé un **sistema de sincronización bidireccional** que permitía:
1. **Módulos nuevos (.NET Core)** escribían en la base de datos nueva
2. **Trigger automático** sincronizaba datos hacia la base de datos legacy
3. **Módulos antiguos (ASP 3.0)** continuaban funcionando normalmente
4. **Zero downtime** durante toda la migración
```csharp
// Ejemplo de sincronización implementada
public class DualWriteService
{
public async Task SaveDriver(Driver driver)
{
// Escribe en base de datos nueva (.NET Core)
await _newDbContext.Drivers.AddAsync(driver);
await _newDbContext.SaveChangesAsync();
// Trigger SQL sincroniza automáticamente hacia base de datos legacy
// Módulos ASP 3.0 continúan funcionando
}
}
```
**¿Por qué este enfoque?**
- Permitió migración **módulo por módulo**
- Equipo podía continuar desarrollando
- Rollback sencillo si fuera necesario
- Reducción de riesgo operacional
---
### Fase 2: Migración Gradual de Módulos (Meses 4-12)
Migré los módulos en orden de complejidad creciente:
**Orden de migración:**
1. ✅ Registros básicos (conductores, vehículos)
2. ✅ Evaluación de riesgo (integración con base policial)
3. ✅ Gestión de cargas y rutas
4. ✅ Monitoreo GPS en tiempo real
5. ✅ Alertas y notificaciones
6. ✅ Reportes y analytics
**Stack de la aplicación migrada:**
- `.NET Core 1.0` (2015-2016 era el inicio de .NET Core)
- `Entity Framework Core`
- `SignalR` para monitoreo real-time
- `SQL Server 2014`
- APIs RESTful
---
### Fase 3: Reducción de Costos con OSRM (Ahorro de $20k/año)
#### Problema: Costo Prohibitivo de Google Maps
La empresa gastaba **$20,000/año** solo en Google Maps Directions API para cálculo de rutas de camiones.
#### Solución: OSRM (Open Source Routing Machine)
Implementé una solución basada en **OSRM** (motor de ruteo open-source):
**Arquitectura de la solución:**
```
┌─────────────────┐
│ Frontend │
│ (Leaflet.js) │
└────────┬────────┘
┌─────────────────┐ ┌──────────────┐
│ API Wrapper │─────▶│ OSRM Server │
│ (.NET Core) │ │ (self-hosted)│
└────────┬────────┘ └──────────────┘
┌─────────────────┐
│ Google Maps │
│ (display only) │
└─────────────────┘
```
**Implementación:**
1. **Servidor OSRM configurado** en servidor propio
2. **API wrapper amigable** en .NET Core que:
- Recibía origen/destino
- Consultaba OSRM (gratuito)
- Devolvía todos los puntos de la ruta
- Formateaba para el frontend
3. **Frontend** dibujaba la ruta en Google Maps (solo visualización, sin API de rutas)
```csharp
[HttpGet("route")]
public async Task<IActionResult> GetRoute(double originLat, double originLng,
double destLat, double destLng)
{
// Consulta OSRM (gratuito)
var osrmResponse = await _osrmClient.GetRouteAsync(
originLat, originLng, destLat, destLng);
// Retorna puntos formateados para el frontend
return Ok(new {
points = osrmResponse.Routes[0].Geometry.Coordinates,
distance = osrmResponse.Routes[0].Distance,
duration = osrmResponse.Routes[0].Duration
});
}
```
**Frontend con Leaflet:**
```javascript
// Dibuja ruta en el mapa (Google Maps solo para tiles)
L.polyline(routePoints, {color: 'red'}).addTo(map);
```
#### Intento con OpenStreetMap
Intenté sustituir también Google Maps (tiles) por **OpenStreetMap**, que funcionó técnicamente, pero:
**A los usuarios no les gustó** la apariencia
❌ Preferían la interfaz familiar de Google Maps
**Decisión:** Mantener Google Maps solo para visualización (sin costo de API de rutas)
**Resultado:** Ahorro de **~$20,000/año** manteniendo calidad de las rutas.
---
## Resultados e Impacto
### Migración Completa en 12 Meses
**100% de los módulos** migrados de ASP 3.0 a .NET Core
**Zero downtime** durante toda la migración
**Equipo productivo** durante todo el proceso
✅ Sistema más rápido y escalable
### Reducción de Costos
💰 **$20,000/año ahorrados** con sustitución de Google Maps Directions API
📉 **Infraestructura optimizada** con SQL Server 2014
### Mejoras Técnicas
🚀 **Performance:** Aplicación .NET Core 3x más rápida que ASP 3.0
🔒 **Seguridad:** Stack moderno con parches de seguridad activos
🛠️ **Mantenibilidad:** Código C# moderno vs VBScript legacy
📊 **Monitoreo:** SignalR para tracking real-time más eficiente
---
## Fase No Ejecutada: Microservicios & Cloud
### Planificación Inicial
Participé en el **diseño y concepción** de la segunda fase (nunca ejecutada):
**Arquitectura planificada:**
- Migración a **Azure** (cloud estaba apenas comenzando en 2015)
- División en **microservicios**:
- Servicio de autenticación
- Servicio de GPS/tracking
- Servicio de rutas
- Servicio de notificaciones
- **Event-driven architecture** con message queues
**Por qué no fue ejecutada:**
Salí de la empresa inmediatamente después de concluir la migración a .NET Core. La segunda fase quedó planificada pero no fue implementada por mí.
---
## Tech Stack
`ASP 3.0` `VBScript` `.NET Core 1.0` `C#` `Entity Framework Core` `SQL Server 2005` `SQL Server 2014` `OSRM` `Leaflet.js` `Google Maps` `SignalR` `REST APIs` `GPS/Satellite` `Migration Strategy` `Dual-Write Pattern`
---
## Decisiones Clave & Trade-offs
### ¿Por qué sincronización dual-write?
**Alternativas consideradas:**
1. ❌ Big Bang migration (demasiado arriesgado)
2. ❌ Mantener todo en ASP 3.0 (insostenible)
3. ✅ **Migración gradual con sync** (elegido)
**Justificación:**
- Sistema crítico no podía detenerse
- Permitió rollback módulo por módulo
- Equipo continuó productivo
### ¿Por qué OSRM en vez de otros?
**Alternativas:**
- Google Maps: $20k/año ❌
- Mapbox: Licencia paga ❌
- GraphHopper: Configuración compleja ❌
- **OSRM: Open-source, rápido, configurable**
### ¿Por qué no OpenStreetMap para tiles?
**Decisión basada en UX:**
- Técnicamente funcionó perfectamente
- Usuarios preferían interfaz familiar de Google
- **Compromiso:** Google Maps para visualización (gratis) + OSRM para rutas (gratis)
---
## Lecciones Aprendidas
### 1. Migración Gradual > Big Bang
Migrar módulo por módulo con sincronización permitió:
- Aprendizaje continuo
- Ajustes de ruta durante el proceso
- Confianza del equipo y stakeholders
### 2. Open Source Puede Ahorrar Mucho
OSRM ahorró **$20k/año** sin pérdida de calidad. Pero requiere:
- Expertise para configurar
- Infraestructura propia
- Mantenimiento continuo
### 3. UX > Tecnología A Veces
OpenStreetMap era técnicamente superior (gratuito), pero usuarios prefirieron Google Maps. **Lección:** Escuchar a los usuarios finales.
### 4. Planifique Cloud, pero Valide el ROI
En 2015, cloud estaba comenzando. La infraestructura on-premises (cluster SQL Server) era perfectamente capaz. **No fuerce cloud si no hay beneficio claro.**
---
## Contexto: Por qué 2015 fue un Momento Especial
**Estado de la tecnología en 2015:**
- ☁️ **Cloud en pañales:** AWS existía, Azure creciendo, pero adopción corporativa aún baja
- 🆕 **.NET Core 1.0 lanzado** en junio/2016 (usamos RC durante proyecto)
- 📱 **Microservicios:** Concepto nuevo, Docker en adopción inicial
- 🗺️ **Google Maps dominante:** APIs pagas, pocas alternativas open-source maduras
**Desafíos de la época:**
- Herramientas de migración ASP→.NET inexistentes
- Documentación .NET Core escasa (versión 1.0!)
- Patrones de arquitectura aún consolidándose
Este proyecto fue **pionero** al adoptar .NET Core al inicio, cuando la mayoría migraba a .NET Framework 4.x.
---
**Resultado:** Migración exitosa de sistema crítico 24/7, ahorro de $20k/año, y base sólida para evolución futura.
[¿Quiere discutir una migración similar? Póngase en contacto](#contact)

View File

@ -0,0 +1,382 @@
---
title: "CNPJ Fast - Proceso de Migración a CNPJ Alfanumérico"
slug: "cnpj-fast-process"
summary: "Creación de metodología estructurada para migración de aplicaciones al nuevo formato de CNPJ alfanumérico brasileño, vendida a aseguradora y empresa de cobranza."
client: "Empresa de Consultoría (Interno)"
industry: "Consultoría & Transformación Digital"
timeline: "3 meses (creación del proceso)"
role: "Solution Architect & Process Designer"
image: ""
tags:
- Process Design
- CNPJ
- Migration Strategy
- Regulatory Compliance
- Consulting
- Sales Enablement
featured: true
order: 3
date: 2024-09-01
seo_title: "CNPJ Fast - Metodología de Migración CNPJ Alfanumérico"
seo_description: "Caso de creación de proceso estructurado para migración a CNPJ alfanumérico brasileño, vendido a aseguradora y empresa de cobranza."
seo_keywords: "CNPJ alfanumérico, migration process, regulatory compliance, consulting, methodology"
---
## Descripción General
Con la introducción del **CNPJ alfanumérico** por la Receita Federal brasileña, las empresas enfrentaban el desafío de adaptar sus aplicaciones legacy que almacenaban CNPJ como campos numéricos (`bigint`, `numeric`, `int`).
Creé **CNPJ Fast**, una metodología estructurada para evaluar, planificar y ejecutar migraciones de CNPJ en aplicaciones y bases de datos corporativas.
**Resultado:** Proceso vendido a **2 clientes** (aseguradora y empresa de cobranza) antes incluso de la implementación.
---
## Desafío
### Cambio Regulatorio Complejo
**Contexto regulatorio:**
- Receita Federal brasileña introdujo **CNPJ alfanumérico**
- CNPJ deja de ser solo números (14 dígitos)
- Pasa a aceptar **letras y números** (formato alfanumérico)
**Impacto en las empresas:**
```sql
-- ANTES: CNPJ numérico
CNPJ BIGINT -- 12345678000190
-- DESPUÉS: CNPJ alfanumérico
CNPJ VARCHAR(18) -- 12.ABC.678/0001-90
```
**Problemas identificados:**
1. 🗄️ **Base de datos:** Columnas `BIGINT`, `NUMERIC`, `INT` no soportan caracteres
2. 🔑 **Claves primarias:** CNPJ usado como PK en varias tablas
3. 🔗 **Foreign keys:** Relaciones entre tablas
4. 📊 **Volumen:** Millones de registros para migrar
5. 💻 **Aplicaciones:** Validaciones, máscaras, reglas de negocio
6. 🧪 **Pruebas:** Garantizar integridad después de migración
7. ⏱️ **Downtime:** Ventanas de mantenimiento limitadas
**Sin un proceso estructurado**, empresas arriesgaban:
- Pérdida de datos
- Inconsistencias en la base de datos
- Aplicaciones rotas
- Downtime prolongado
---
## Solución: CNPJ Fast Process
### Metodología en 5 Fases
Diseñé un proceso estructurado que podría ser replicado en diferentes clientes:
```
┌─────────────────────────────────────────────┐
│ FASE 1: DISCOVERY & ASSESSMENT │
│ - Inventario de aplicaciones │
│ - Análisis de schemas de base de datos │
│ - Identificación de tablas impactadas │
│ - Estimación de volumen de datos │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 2: IMPACT ANALYSIS │
│ - Mapeo de dependencias │
│ - Análisis de claves primarias/foráneas │
│ - Identificación de reglas de negocio │
│ - Evaluación de riesgo │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 3: MIGRATION PLANNING │
│ - Estrategia de migración (phased commits) │
│ - Scripts SQL automatizados │
│ - Plan de rollback │
│ - Ventanas de mantenimiento │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 4: EXECUTION │
│ - Migración de datos en lotes │
│ - Actualización de aplicaciones │
│ - Pruebas de integración │
│ - Validación de integridad │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 5: VALIDATION & GO-LIVE │
│ - Pruebas de regresión │
│ - Validación de performance │
│ - Go-live coordinado │
│ - Monitoreo post-migración │
└─────────────────────────────────────────────┘
```
---
### Fase 1: Discovery & Assessment
**Objetivo:** Entender el alcance completo de la migración
**Entregables:**
1. **Inventario de Aplicaciones**
- Lista de aplicaciones que usan CNPJ
- Tecnologías (ASP 3.0, VB6, .NET, microservicios)
- Criticidad de cada aplicación
2. **Análisis de Schema**
```sql
-- Script de descubrimiento automático
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
ON t.TABLE_NAME = c.TABLE_NAME
WHERE c.COLUMN_NAME LIKE '%CNPJ%'
AND c.DATA_TYPE IN ('bigint', 'numeric', 'int')
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME;
```
3. **Estimación de Volumen**
- Total de registros por tabla
- Tamaño en GB
- Tiempo estimado de migración
**Ejemplo de output:**
| Tabla | Columna | Tipo Actual | Registros | Criticidad |
|--------|--------|------------|-----------|-------------|
| Clientes | CNPJ_Cliente | BIGINT | 8.000.000 | Alta |
| Proveedores | CNPJ_Proveedor | NUMERIC(14) | 2.500.000 | Media |
| Transacciones | CNPJ_Pagador | BIGINT | 90.000.000 | Crítica |
---
### Fase 2: Impact Analysis
**Objetivo:** Mapear todas las dependencias y riesgos
**Análisis de claves:**
```sql
-- Identifica PKs y FKs que involucran CNPJ
SELECT
fk.name AS FK_Name,
tp.name AS Parent_Table,
cp.name AS Parent_Column,
tr.name AS Referenced_Table,
cr.name AS Referenced_Column
FROM sys.foreign_keys fk
INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
INNER JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id
AND fkc.parent_object_id = cp.object_id
INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id
AND fkc.referenced_object_id = cr.object_id
WHERE cp.name LIKE '%CNPJ%' OR cr.name LIKE '%CNPJ%';
```
**Evaluación de Riesgo:**
- 🔴 **Alto:** Tablas con CNPJ como PK y >10M registros
- 🟡 **Medio:** Tablas con FK hacia CNPJ
- 🟢 **Bajo:** Tablas sin constraints
---
### Fase 3: Migration Planning
**Estrategia de migración gradual:**
Para evitar bloqueo de base de datos, diseñé estrategia de **phased commits**:
```sql
-- Estrategia para tablas grandes (>1M registros)
-- 1. Agregar nueva columna VARCHAR
ALTER TABLE Clientes
ADD CNPJ_Cliente_New VARCHAR(18) NULL;
-- 2. Migración en lotes (commits faseados)
DECLARE @BatchSize INT = 100000;
DECLARE @RowsAffected INT = 1;
WHILE @RowsAffected > 0
BEGIN
UPDATE TOP (@BatchSize) Clientes
SET CNPJ_Cliente_New = FORMAT(CNPJ_Cliente, '00000000000000')
WHERE CNPJ_Cliente_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
WAITFOR DELAY '00:00:01'; -- Pausa entre lotes
END;
-- 3. Remover constraints (PKs, FKs)
ALTER TABLE Clientes DROP CONSTRAINT PK_Clientes;
-- 4. Renombrar columnas
EXEC sp_rename 'Clientes.CNPJ_Cliente', 'CNPJ_Cliente_Old', 'COLUMN';
EXEC sp_rename 'Clientes.CNPJ_Cliente_New', 'CNPJ_Cliente', 'COLUMN';
-- 5. Recrear constraints
ALTER TABLE Clientes
ADD CONSTRAINT PK_Clientes PRIMARY KEY (CNPJ_Cliente);
-- 6. Remover columna antigua (tras validación)
ALTER TABLE Clientes DROP COLUMN CNPJ_Cliente_Old;
```
**¿Por qué este enfoque?**
- ✅ Evita lock de tabla entera
- ✅ Permite pausar/reanudar migración
- ✅ Minimiza impacto en producción
- ✅ Facilita rollback si es necesario
---
### Fase 4 & 5: Execution y Validation
**Checklist de ejecución:**
- [ ] Backup completo de la base de datos
- [ ] Ejecutar scripts de migración en lotes
- [ ] Actualizar aplicaciones (validaciones, máscaras)
- [ ] Pruebas de integración
- [ ] Validación de integridad referencial
- [ ] Pruebas de performance
- [ ] Go-live coordinado
- [ ] Monitoreo 24h post-migración
---
## Sales Enablement: Presentación UX
**Colaboración con Gestor de UX:**
El gestor de UX de la empresa creó una **presentación visual impactante** del proceso CNPJ Fast:
**Contenido de la presentación:**
- 📊 Infografías del proceso de 5 fases
- 📈 Ejemplos de estimaciones de tiempo/costo
- 🎯 Casos de uso (aseguradoras, bancos, fintechs)
- ✅ Checklist ejecutivo
- 📋 Templates de documentación
**Resultado:** Presentación utilizada por el equipo comercial para prospección.
---
## Resultados e Impacto
### Ventas Realizadas
**Cliente 1: Aseguradora**
- Stack: ASP 3.0, VB6 components, .NET, microservicios
- Alcance: Migración completa de aplicaciones legacy
- Estado: **Proyecto vendido** (ejecución por otro equipo)
- Valor: [Confidencial]
**Cliente 2: Empresa de Cobranza**
- Alcance: Migración de base de datos (~100M registros)
- Estado: **Proyecto vendido y en ejecución** (por mí)
- Particularidad: Proceso fue **reestructurado** para atender necesidades específicas
- Ver caso completo: [Migración CNPJ - 100M Registros](/cases/cnpj-migration-database)
---
### Impacto en el Negocio
💰 **2 proyectos vendidos** antes incluso de la primera ejecución
📈 **Proceso replicable** para nuevos clientes
🎯 **Posicionamiento** como especialista en migraciones regulatorias
📚 **Base de conocimiento** para futuros proyectos similares
---
### Impacto Técnico
🔧 **Metodología probada** en escenarios reales
📖 **Documentación reutilizable** (scripts, checklists, templates)
🚀 **Aceleración** de proyectos similares (de semanas a días)
---
## Tech Stack
`SQL Server` `Migration Strategy` `Process Design` `Regulatory Compliance` `ASP 3.0` `VB6` `.NET` `Microservices` `Batch Processing` `Database Optimization`
---
## Decisiones Clave & Trade-offs
### ¿Por qué proceso estructurado?
**Alternativas:**
1. ❌ Enfoque ad-hoc por proyecto
2. ❌ Consultoría manual sin metodología
3. ✅ **Proceso replicable y escalable**
**Justificación:**
- Reduce tiempo de Discovery
- Estandariza entregas
- Facilita ventas (presentación lista)
- Permite ejecución por diferentes equipos
### ¿Por qué separar en 5 fases?
**Beneficios:**
- Cliente puede aprobar fase a fase
- Permite ajustes durante el proceso
- Facilita gestión de riesgos
- Entregas incrementales
---
## Lecciones Aprendidas
### 1. UX/Presentación Importa para Ventas
La presentación visual hecha por el gestor de UX fue **crucial** para cerrar los 2 contratos. Proceso técnico bueno + presentación mala = sin ventas.
### 2. Proceso Vende, No Solo Ejecución
Crear una **metodología documentada** tiene más valor comercial que solo ofrecer "horas de consultoría".
### 3. Cada Cliente es Único
El cliente solicitó **reestructuración del proceso**. Un buen proceso debe ser:
- Estructurado lo suficiente para ser replicable
- Flexible lo suficiente para personalizar
### 4. Colaboración Multidisciplinaria
Trabajar con gestor de UX (presentaciones) + equipo comercial (ventas) + técnico (ejecución) = éxito.
---
## Próximos Pasos
**Oportunidades futuras:**
1. 🌎 **Expansión:** Ofrecer CNPJ Fast para más sectores (bancos, fintechs, retail)
2. 📦 **Producto:** Transformar en herramienta automatizada (SaaS)
3. 📚 **Capacitación:** Capacitar equipos internos de clientes
4. 🔄 **Evolución:** Adaptar proceso para otras migraciones regulatorias (PIX, Open Banking)
---
**Resultado:** Metodología estructurada que se convirtió en producto vendible, generando ingresos antes incluso de la primera ejecución técnica.
[¿Quiere implementar CNPJ Fast en su empresa? Póngase en contacto](#contact)

View File

@ -0,0 +1,469 @@
---
title: "Migración CNPJ Alfanumérico - 100 Millones de Registros"
slug: "cnpj-migration-database"
summary: "Ejecución de migración masiva de CNPJ numérico a alfanumérico en base de datos con ~100M registros, usando estrategia de commits faseados para evitar bloqueo."
client: "Empresa de Cobranza"
industry: "Cobranza & Servicios Financieros"
timeline: "En ejecución"
role: "Database Architect & Tech Lead"
image: ""
tags:
- SQL Server
- Database Migration
- CNPJ
- Performance Optimization
- Batch Processing
- Big Data
featured: true
order: 4
date: 2024-11-01
seo_title: "Migración CNPJ Alfanumérico - 100M Registros | Carneiro Tech"
seo_description: "Caso de migración masiva de CNPJ en base de datos con 100 millones de registros usando commits faseados y optimizaciones de performance."
seo_keywords: "database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits"
---
## Descripción General
Una empresa de cobranza que trabaja con bases de datos de información transitoria (sin software propietario) necesita adaptar sus sistemas al nuevo formato de **CNPJ alfanumérico** brasileño.
**Desafío principal:** Migrar ~**100 millones de registros** en tablas con columnas `BIGINT` y `NUMERIC` a `VARCHAR`, sin bloquear la base de datos en producción.
**Estado:** Proyecto en ejecución (preparación de scripts de migración).
---
## Desafío
### Volumen Masivo de Datos
**Contexto de la empresa:**
- Empresa de cobranza (no desarrolla software propio)
- Trabaja con **datos transitorios** (alta rotación)
- Base de datos SQL Server con volumen crítico
**Análisis inicial reveló:**
| Tabla | Columna | Tipo Actual | Registros | Tamaño |
|--------|--------|------------|-----------|---------|
| Deudores | CNPJ_Deudor | BIGINT | 8.000.000 | 60 GB |
| Transacciones | CNPJ_Pagador | NUMERIC(14) | 90.000.000 | 1.2 TB |
| Empresas | CNPJ_Empresa | BIGINT | 2.500.000 | 18 GB |
| **TOTAL** | - | - | **~100.000.000** | **~1.3 TB** |
**Problemas identificados:**
1. 🔴 **Tablas con 8M+ líneas** usando `BIGINT` para CNPJ
2. 🔴 **90 millones de registros** en tabla de transacciones
3. 🔑 **CNPJ como clave primaria** en algunas tablas
4. 🔗 **Foreign keys** relacionando múltiples tablas
5. ⚠️ **Imposibilidad de downtime prolongado** (operación 24/7)
6. 💾 **Restricciones de espacio** en disco (necesita estrategia eficiente)
---
## Decisión Estratégica: Phased Commits
### ¿Por qué NO hacer ALTER COLUMN directo?
**Enfoque ingenuo (NO funciona):**
```sql
-- ❌ NUNCA HAGA ESTO EN TABLAS GRANDES
ALTER TABLE Transacciones
ALTER COLUMN CNPJ_Pagador VARCHAR(18);
```
**Problemas:**
- 🔒 Bloquea la tabla entera durante la conversión
- ⏱️ Puede tomar horas/días en tablas grandes
- 💥 Bloquea todas las operaciones (INSERT, UPDATE, SELECT)
- 🚨 Riesgo de timeout o falla en medio de la operación
- 🔙 Rollback complejo si algo sale mal
---
### Estrategia Elegida: Column Swap con Commits Faseados
**Basado en experiencia anterior**, decidí usar enfoque gradual:
```
┌─────────────────────────────────────────────┐
│ 1. Crear nueva columna VARCHAR al FINAL │
│ (operación rápida, no bloquea tabla) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 2. UPDATE en lotes (commits faseados) │
│ - 100k registros a la vez │
│ - Pausa entre lotes (evita contención) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 3. Remover PKs y FKs │
│ (tras 100% migrado) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 4. Renombrar columnas (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 5. Recrear PKs/FKs con nueva columna │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 6. Validación y eliminación columna vieja │
└─────────────────────────────────────────────┘
```
**¿Por qué este enfoque?**
**Sin lock de tabla completa** (operación incremental)
**Puede pausar/reanudar** en cualquier momento
**Monitoreo de progreso** en tiempo real
**Rollback simple** (basta eliminar nueva columna)
**Minimiza impacto en producción** (commits pequeños)
**Decisión tomada basada en:**
- 📚 Experiencia anterior con migraciones de gran volumen
- 🔍 Conocimiento de locks de SQL Server
- 🎯 Necesidad de zero downtime
**Nota:** Esta decisión fue tomada **sin consultar IA** - basada puramente en experiencia práctica de proyectos anteriores.
---
## Detalles de Implementación
### Fase 1: Crear Nueva Columna
```sql
-- Operación rápida (metadata change solamente)
ALTER TABLE Transacciones
ADD CNPJ_Pagador_New VARCHAR(18) NULL;
-- Agrega índice temporal para acelerar lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transacciones(CNPJ_Pagador_New)
WHERE CNPJ_Pagador_New IS NULL;
```
**Tiempo estimado:** ~1 segundo (independiente del tamaño de la tabla)
---
### Fase 2: Migración en Lotes (Core Strategy)
```sql
-- Script de migración con commits faseados
DECLARE @BatchSize INT = 100000; -- 100k registros por lote
DECLARE @RowsAffected INT = 1;
DECLARE @TotalProcessed INT = 0;
DECLARE @StartTime DATETIME = GETDATE();
WHILE @RowsAffected > 0
BEGIN
BEGIN TRANSACTION;
-- Actualiza lote de 100k registros aún no migrados
UPDATE TOP (@BatchSize) Transacciones
SET CNPJ_Pagador_New = RIGHT('00000000000000' + CAST(CNPJ_Pagador AS VARCHAR), 14)
WHERE CNPJ_Pagador_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
SET @TotalProcessed = @TotalProcessed + @RowsAffected;
COMMIT TRANSACTION;
-- Log de progreso
PRINT 'Processed: ' + CAST(@TotalProcessed AS VARCHAR) + ' rows. Batch: ' + CAST(@RowsAffected AS VARCHAR);
PRINT 'Elapsed time: ' + CAST(DATEDIFF(SECOND, @StartTime, GETDATE()) AS VARCHAR) + ' seconds';
-- Pausa entre lotes (reduce contención)
WAITFOR DELAY '00:00:01'; -- 1 segundo entre lotes
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
```
**Parámetros configurables:**
- `@BatchSize`: 100k (balanceado entre performance y lock time)
- Muy pequeño = muchas transacciones, overhead
- Muy grande = lock prolongado, impacto en prod
- `WAITFOR DELAY`: 1 segundo (da tiempo a otras queries para ejecutar)
**Estimaciones de tiempo:**
| Registros | Batch Size | Tiempo Estimado |
|-----------|------------|----------------|
| 8.000.000 | 100.000 | ~2-3 horas |
| 90.000.000 | 100.000 | ~20-24 horas |
**Ventajas:**
- ✅ No bloquea aplicación
- ✅ Otras queries pueden ejecutar entre los lotes
- ✅ Puede pausar (Ctrl+C) y reanudar después (WHERE NULL toma desde donde paró)
- ✅ Log de progreso en tiempo real
---
### Fase 3: Remoción de Constraints
```sql
-- Identifica todas las PKs y FKs que involucran la columna
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transacciones')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Pagador';
-- Remueve PKs
ALTER TABLE Transacciones
DROP CONSTRAINT PK_Transacciones_CNPJ;
-- Remueve FKs (tablas que referencian)
ALTER TABLE Pagos
DROP CONSTRAINT FK_Pagos_Transacciones;
```
**Tiempo estimado:** ~10 minutos (depende de cuántas constraints existen)
---
### Fase 4: Column Swap (Renombramiento)
```sql
-- Renombra columna antigua a _Old
EXEC sp_rename 'Transacciones.CNPJ_Pagador', 'CNPJ_Pagador_Old', 'COLUMN';
-- Renombra nueva columna al nombre original
EXEC sp_rename 'Transacciones.CNPJ_Pagador_New', 'CNPJ_Pagador', 'COLUMN';
-- Altera a NOT NULL (tras validación de 100% completado)
ALTER TABLE Transacciones
ALTER COLUMN CNPJ_Pagador VARCHAR(18) NOT NULL;
```
**Tiempo estimado:** ~1 segundo (metadata change)
---
### Fase 5: Recreación de Constraints
```sql
-- Recrea PK con nueva columna VARCHAR
ALTER TABLE Transacciones
ADD CONSTRAINT PK_Transacciones_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Pagador);
-- Recrea FKs
ALTER TABLE Pagos
ADD CONSTRAINT FK_Pagos_Transacciones
FOREIGN KEY (CNPJ_Pagador) REFERENCES Transacciones(CNPJ_Pagador);
```
**Tiempo estimado:** ~30-60 minutos (depende del volumen)
---
### Fase 6: Validación y Limpieza
```sql
-- Valida que 100% fue migrado
SELECT COUNT(*)
FROM Transacciones
WHERE CNPJ_Pagador IS NULL OR CNPJ_Pagador = '';
-- Valida integridad referencial
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- Si todo OK, remueve columna antigua
ALTER TABLE Transacciones
DROP COLUMN CNPJ_Pagador_Old;
-- Remueve índice temporal
DROP INDEX IX_Temp_CNPJ_New ON Transacciones;
```
---
## Personalización del Proceso CNPJ Fast
### Diferencias vs. Proceso Original
El proceso **CNPJ Fast** original fue **reestructurado** para este cliente:
**Cambios principales:**
| Aspecto | CNPJ Fast Original | Cliente (Personalizado) |
|---------|-------------------|---------------------|
| **Foco** | Aplicaciones + DB | Solo DB (sin software propio) |
| **Discovery** | Inventario de apps | Solo análisis de schema |
| **Ejecución** | Múltiples aplicaciones | Scripts SQL masivos |
| **Batch Size** | 50k-100k | 100k (optimizado para volumen) |
| **Monitoreo** | Manual + herramientas | Logs SQL en tiempo real |
| **Rollback** | Proceso complejo | Simple (DROP COLUMN) |
**Motivo de la reestructuración:**
- Cliente no tiene aplicaciones propias (solo consume datos)
- Foco 100% en optimización de base de datos
- Volumen mucho mayor que casos típicos (100M vs ~10M)
---
## Tech Stack
`SQL Server` `T-SQL` `Batch Processing` `Performance Tuning` `Database Optimization` `Migration Scripts` `Phased Commits` `Index Optimization` `Constraint Management`
---
## Decisiones Clave & Trade-offs
### ¿Por qué 100k por batch?
**Pruebas de performance:**
| Batch Size | Tiempo/Batch | Lock Duration | Contención |
|------------|-------------|---------------|-----------|
| 10.000 | 2s | Bajo | ✅ Mínimo |
| 50.000 | 8s | Medio | ✅ Aceptable |
| **100.000** | 15s | **Medio** | **✅ Balanceado** |
| 500.000 | 90s | Alto | ❌ Impacto en prod |
| 1.000.000 | 180s | Muy alto | ❌ Inaceptable |
**Elección:** 100k ofrece mejor balance entre performance e impacto.
---
### ¿Por qué crear columna al FINAL?
**Internals de SQL Server:**
- Agregar columna al final = metadata change (rápido)
- Agregar en medio = reescritura de páginas (lento)
- Para tablas grandes, posición importa
---
### ¿Por qué WAITFOR DELAY de 1 segundo?
**Sin delay:**
- ❌ Batch processing consume 100% del I/O
- ❌ Queries de aplicación se vuelven lentas
- ❌ Lock escalation puede ocurrir
**Con delay de 1s:**
- ✅ Otras queries tienen ventana para ejecutar
- ✅ I/O distribuido
- ✅ Experiencia del usuario preservada
**Trade-off:** Migración toma +1s por batch (~25% más lenta), pero sistema permanece responsivo.
---
## Estado Actual & Próximos Pasos
### Estado Actual (Diciembre 2024)
📝 **Fase de Preparación:**
- ✅ Discovery completo (100M registros identificados)
- ✅ Scripts de migración desarrollados
- ✅ Pruebas en ambiente de homologación
- 🔄 Validación de performance
- ⏳ Esperando ventana de mantenimiento para producción
### Próximos Pasos
1. **Backup completo** de producción
2. **Ejecución en producción** (ambiente 24/7)
3. **Monitoreo en tiempo real** durante migración
4. **Validación post-migración** (integridad, performance)
5. **Documentación de lessons learned**
---
## Lecciones Aprendidas (Hasta Ahora)
### 1. Experiencia Anterior Vale Oro
Decisión de usar phased commits vino de **experiencia práctica** en proyectos anteriores, no de documentación o IA.
**Situaciones similares anteriores:**
- Migración de datos en e-commerce (50M registros)
- Conversión de encoding (UTF-8 en 100M+ rows)
- Particionamiento de tablas históricas
---
### 2. "Measure Twice, Cut Once"
Antes de ejecutar en producción:
- ✅ Pruebas exhaustivas en homologación
- ✅ Scripts validados y revisados
- ✅ Rollback probado
- ✅ Estimaciones de tiempo confirmadas
**Tiempo de preparación:** 3 semanas
**Tiempo de ejecución:** Estimado en 48 horas
**Ratio:** 10:1 (preparación vs ejecución)
---
### 3. Personalización > One-Size-Fits-All
El proceso CNPJ Fast original necesitó ser **reestructurado** para este cliente.
**Lección:** Los procesos deben ser:
- Estructurados lo suficiente para repetir
- Flexibles lo suficiente para adaptar
---
### 4. Monitoreo es Crucial
Scripts con **log detallado** de progreso permiten:
- Estimar tiempo restante
- Identificar cuellos de botella
- Pausar/reanudar con confianza
- Reportar estado a stakeholders
```sql
-- Log example
Processed: 10.000.000 rows. Batch: 100.000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
```
---
## Optimizaciones de Performance
### Optimizaciones Implementadas
1. **Índice temporal WHERE NULL**
- Acelera lookup de registros no migrados
- Removido tras conclusión
2. **Batch size optimizado**
- Balanceado entre performance y lock time
3. **Transaction log management**
```sql
-- Verificar crecimiento del log
DBCC SQLPERF(LOGSPACE);
-- Ajustar recovery model (si permitido)
ALTER DATABASE MyDatabase SET RECOVERY SIMPLE;
```
4. **Ejecución en horario de menor carga**
- Ventana de mantenimiento nocturna
- Fin de semana (si es posible)
---
**Resultado esperado:** Migración de 100 millones de registros en ~48 horas, sin downtime significativo y con posibilidad de rollback rápido.
[¿Necesita migrar volúmenes masivos de datos? Póngase en contacto](#contact)

View File

@ -0,0 +1,588 @@
---
title: "Plataforma de Capacitación Industrial - De Wireframes a Sistema Completo"
slug: "industrial-learning-platform"
summary: "Solution Design para plataforma de microlearning en empresa de gases industriales. Identificación de requisitos críticos no mapeados (admin, registros, exportación) antes de la presentación al cliente, evitando retrabajo y garantizando usabilidad real."
client: "Empresa de Gases Industriales"
industry: "Industrial & Manufactura"
timeline: "4 meses"
role: "Solution Architect & Tech Lead"
image: ""
tags:
- Solution Design
- EdTech
- Learning Platform
- Requirements Analysis
- Tech Lead
- User Stories
- .NET
- Product Design
featured: true
order: 5
date: 2024-06-01
seo_title: "Plataforma de Capacitación Industrial - Solution Design"
seo_description: "Caso de Solution Design para plataforma de microlearning, identificando requisitos críticos antes de la presentación al cliente y liderando desarrollo hasta producción."
seo_keywords: "solution design, learning platform, microlearning, requirements analysis, tech lead, industrial training"
---
## Descripción General
Empresa de gases industriales solicita plataforma para capacitar empleados usando metodología de **microlearning** (contenidos cortos y objetivos).
**Requisito inicial:** "Queremos solo la estructura - ruta de aprendizaje, microlearning, pregunta de test y puntuación."
**Problema:** Especificación incompleta que resultaría en sistema **imposible de usar** (sin forma de registrar contenido, sin administradores, sin exportar resultados).
**Solución:** Análisis crítico de requisitos **antes de la presentación al cliente**, identificando gaps funcionales y proponiendo solución completa.
---
## Desafío
### Wireframes Bonitos, Funcionalidad Incompleta
**Situación inicial:**
UX creó wireframes hermosos mostrando:
- ✅ Rutas de aprendizaje
- ✅ Microlearnings (video/texto + imagen)
- ✅ Preguntas de test (opción múltiple)
- ✅ Puntuación por empleado
**Problema identificado:**
Nadie (cliente, UX, comercial) pensó en:
**¿Cómo entra contenido en el sistema?**
- ¿Quién registra rutas?
- ¿Quién crea microlearnings?
- ¿Quién escribe preguntas?
- ¿Interfaz manual o import?
**¿Quién gestiona el sistema?**
- ¿Existe concepto de admin?
- ¿RRHH puede crear admins?
- ¿Gestor de área puede ver solo su equipo?
**¿Cómo salen datos del sistema?**
- RRHH necesita reportes
- Compliance necesita evidencias
- ¿Cómo exportar datos?
- ¿Formato: Excel? PDF? API?
**Riesgo real:**
Si desarrolláramos exactamente lo que fue pedido:
- Sistema funcionaría técnicamente ✅
- **Pero sería completamente inutilizable**
- Cliente tendría que pagar refacción para incluir CRUD básico
- Retrabajo + costo adicional + frustración
---
## Proceso de Solution Design
### Etapa 1: Análisis Crítico (Antes de la Presentación)
**Acción tomada:** Convoqué reunión con UX **antes** de presentar al cliente.
**Puntos levantados:**
**"¿Cómo entra el primer contenido al sistema?"**
- UX: "Ah... no pensamos en eso. ¿Ustedes van a poblar la base de datos?"
- Yo: "¿Y cuando cliente quiera agregar nueva ruta? ¿Vamos a alterar BD en producción?"
**"¿Quién es el dueño del sistema?"**
- UX: "RRHH, imagino."
- Yo: "¿Solo una persona? ¿Y si sale de la empresa? ¿Cómo delega?"
**"¿RRHH pidió reportes?"**
- UX: "No fue mencionado en el briefing."
- Yo: "RRHH siempre necesita reportes. Es para compliance (NR, ISO)."
---
### Etapa 2: Requisitos Funcionales Identificados
Propuse 4 módulos adicionales **esenciales**:
#### 1. Sistema de Administración
**Funcionalidades:**
- Usuario estándar: Solo realiza capacitaciones
- Usuario admin: Gestiona contenido + ve reportes
- Admin puede promover otros usuarios a admin
- Control de acceso (admin general vs admin de área)
**Por qué es crítico:**
Sin esto, sistema es estático (contenido nunca se actualiza).
---
#### 2. CRUD de Contenido
**a) Registro de Rutas:**
- Nombre de la ruta
- Descripción
- Orden de los microlearnings
- Ruta activa/inactiva (permite despublicar)
**b) Registro de Microlearnings:**
- Título
- Tipo: Texto simple (2 párrafos) O Video
- Upload de imagen (si texto)
- URL de video (si video)
- Orden dentro de la ruta
**c) Registro de Preguntas:**
- Pregunta (texto)
- 3 opciones de respuesta:
- "Excelente" (verde)
- "Regular" (amarillo)
- "Malo" (rojo)
- Puntuación por respuesta (ej: 10, 5, 0 puntos)
- Feedback personalizado por respuesta
**Por qué es crítico:**
Cliente necesita actualizar contenido sin llamar a dev/DBA.
---
#### 3. Exportación de Datos
**Funcionalidades:**
- Exportar a Excel (.xlsx)
- Filtros:
- Por período (fecha inicio/fin)
- Por ruta
- Por empleado
- Por área/departamento
- Columnas exportadas:
- Nombre del empleado
- Matrícula
- Ruta completada
- Puntuación total
- Fecha de conclusión
- Respuestas individuales (para auditoría)
**Por qué es crítico:**
RRHH necesita evidenciar capacitación para:
- Normas Reglamentarias (NR-13, NR-20 - gases inflamables)
- Auditorías ISO
- Procesos laborales
---
#### 4. Gestión de Usuarios
**Funcionalidades:**
- Importar empleados (upload CSV/Excel)
- Registro manual
- Activar/desactivar usuarios
- Asignar rutas obligatorias por área
- Notificaciones de pendientes
**Por qué es crítico:**
Empresa tiene 500+ empleados, registro manual es inviable.
---
### Etapa 3: Presentación al Cliente
**Abordaje:**
1. Mostré wireframes del UX (interfaz bonita)
2. Pregunté: "¿Cómo van a registrar la primera ruta?"
3. Cliente: "Ah... buena pregunta. No habíamos pensado en eso."
4. Presenté los 4 módulos adicionales
5. Cliente: "Tiene total sentido! Sin esto no podemos usar."
**Resultado:**
- Propuesta aprobada **con módulos adicionales**
- Alcance ajustado (timeline + presupuesto)
- Zero retrabajo futuro
- Cliente reconoció valor agregado
---
## Implementación
### Mi Rol en el Proyecto
**1. Solution Architect**
- Identificación de requisitos no funcionales
- Diseño de arquitectura (módulos, integraciones)
- Definición de tecnologías
**2. Tech Lead**
- Liderazgo técnico del equipo (3 devs)
- Code review
- Definición de estándares de código
- Gestión de riesgos técnicos
**3. Product Owner Técnico**
- Creación de **user stories** completas
- Priorización de backlog
- Refinamiento continuo con cliente
---
### Stack Técnico Elegido
**Backend:**
- `.NET 7` - APIs REST
- `Entity Framework Core` - ORM
- `SQL Server` - Base de datos
- `ClosedXML` - Generación de Excel
**Frontend:**
- `React` - Interfaz web
- `Material-UI` - Componentes
- `React Player` - Player de video
- `Chart.js` - Gráficos de progreso
**Infraestructura:**
- `Azure App Service` - Hospedaje
- `Azure Blob Storage` - Almacenamiento de videos/imágenes
- `Azure SQL Database` - Base de datos gestionada
---
### User Stories Creadas
Escribí **32 user stories** cubriendo todos los flujos. Ejemplos:
**US-01: Registrar Ruta (Admin)**
```
Como administrador del sistema
Quiero registrar una nueva ruta de capacitación
Para que empleados puedan realizar los cursos
Criterios de aceptación:
- Admin accede menú "Rutas" → "Nueva Ruta"
- Completa: Nombre, Descripción, Estado (Activa/Inactiva)
- Puede agregar microlearnings existentes a la ruta
- Define orden de los microlearnings (drag & drop)
- Sistema valida campos obligatorios
- Guarda y muestra mensaje de éxito
```
**US-15: Realizar Microlearning (Empleado)**
```
Como empleado
Quiero realizar un microlearning de mi ruta
Para aprender sobre el tema y ganar puntos
Criterios de aceptación:
- Empleado accede ruta asignada
- Ve lista de microlearnings (no completados primero)
- Hace clic en microlearning → abre pantalla con:
- Texto (2 párrafos) + Imagen O
- Player de video embebido
- Botón "Continuar" aparece después de:
- 30s (si texto)
- Final del video (si video)
- Marca microlearning como visto
- Pregunta de test aparece automáticamente
```
**US-22: Exportar Resultados (Admin)**
```
Como administrador
Quiero exportar resultados de capacitación a Excel
Para generar reportes de compliance y auditorías
Criterios de aceptación:
- Admin accede "Reportes" → "Exportar"
- Selecciona filtros (período, ruta, área)
- Hace clic "Generar Excel"
- Sistema procesa y descarga archivo .xlsx
- Excel contiene columnas: Nombre, Matrícula, Ruta, Puntos, Fecha, Respuestas
- Formato legible (headers en negrita, columnas autoajustadas)
```
---
## Características Clave Implementadas
### 1. Sistema de Puntuación Gamificado
**Mecánica:**
- Cada pregunta vale puntos (configurable)
- Respuesta "Excelente": 10 puntos
- Respuesta "Regular": 5 puntos
- Respuesta "Malo": 0 puntos
**Dashboard del empleado:**
- Puntuación total
- Ranking (opcional, configurable)
- Badges por rutas completadas
- Progreso visual (barra de %)
**Por qué funciona:**
Empleados de planta se enganchan más con elementos de gamificación.
---
### 2. Microlearning Adaptativo
**Tipos de contenido:**
**Texto + Imagen:**
- 2 párrafos (máx 300 palabras)
- 1 imagen ilustrativa
- Ideal para: Procedimientos, normas, conceptos
**Video:**
- Videos cortos (2-5 min)
- Player embebido (YouTube/Vimeo o upload)
- Ideal para: Demostraciones, operaciones de equipo
**¿Por qué microlearning?**
- Empleados realizan en el intervalo (10-15min)
- Contenido corto = mayor retención
- Facilita actualización (vs cursos largos)
---
### 3. Sistema de Administración Delegada
**Jerarquía:**
```
Admin General (RRHH)
↓ puede promover
Admin de Área (Gerentes)
↓ puede visualizar solo
Empleados de su área
```
**Permisos:**
- Admin general: Crea rutas, promueve admins, ve todos los datos
- Admin de área: Ve solo reportes de su área
- Empleado: Solo realiza capacitaciones
**Auditoría:**
- Logs de quién creó/editó cada contenido
- Histórico de promociones a admin
- Compliance SOX/ISO
---
### 4. Exportación para Compliance
**Formato del Excel generado:**
| Matrícula | Nombre | Área | Ruta | Fecha Conclusión | Puntos | Estado |
|-----------|------|------|--------|----------------|--------|--------|
| 1001 | João Silva | Producción | Seguridad NR-20 | 15/11/2024 | 95/100 | ✅ Aprobado |
| 1002 | María Santos | Logística | Manejo Gases | 14/11/2024 | 78/100 | ✅ Aprobado |
**Pestaña adicional: Detalle de Respuestas**
- Permite auditoría: "¿Empleado X acertó pregunta Y?"
- Evidencia para procesos laborales
- Compliance NR-13/NR-20
---
## Resultados e Impacto
### Sistema en Producción
**Estado actual:** En uso hace 4+ meses
**Métricas de adopción:**
- 👥 500+ empleados registrados
- 📚 12 rutas activas
- 📖 150+ microlearnings creados
- ✅ 8.000+ capacitaciones completadas
- 📊 100+ reportes exportados (compliance)
**Tasa de conclusión:** 87% (media industria: 45%)
---
### Impacto en el Cliente
**Antes:**
- Capacitaciones presenciales (costo alto, agenda difícil)
- Evidencias en papel (pérdidas, difícil auditoría)
- Dificultad en actualizar contenido
**Después:**
- Capacitación asíncrona (empleado realiza cuando puede)
- Evidencias digitales (compliance facilitado)
- RRHH actualiza contenido sin llamar a TI
- Reducción del 70% en costo de capacitación
**Feedback del cliente:**
> "Si hubiéramos implementado solo lo que pedimos inicialmente, el sistema sería inútil. El análisis previo salvó el proyecto."
---
### Valor del Solution Design
**ROI del análisis preventa:**
**Escenario A (sin análisis):**
1. Desarrollar solo interfaz (2 meses)
2. Cliente prueba y percibe que falta CRUD (1 mes después)
3. Refacción para agregar módulos (2+ meses)
4. **Total: 5+ meses + frustración del cliente**
**Escenario B (con análisis - lo que hicimos):**
1. Identificar requisitos antes (1 semana)
2. Aprobar alcance completo (1 semana)
3. Desarrollar solución correcta (4 meses)
4. **Total: 4 meses + cliente satisfecho**
**Economía:** 1+ mes de retrabajo + costo de oportunidad
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React` `Material-UI` `Azure App Service` `Azure Blob Storage` `ClosedXML` `Chart.js` `User Stories` `Solution Design` `Tech Lead`
---
## Decisiones Clave & Trade-offs
### ¿Por qué no usar LMS listo? (Moodle, Canvas)
**Alternativas consideradas:**
1. ❌ Moodle (open-source, gratuito)
2. ❌ Totara/Canvas (LMS corporativo)
3. ✅ **Desarrollo custom**
**Justificación:**
- LMS genérico: Complejidad innecesaria (foros, wikis, etc)
- Cliente quiere **solo microlearning** (simplicidad)
- Costo de licencia LMS > costo de dev custom
- Integración con AD/SSO del cliente (más fácil custom)
- UX optimizada para planta (mobile-first, touch)
---
### ¿Por qué 3 opciones de respuesta (vs 4-5)?
**Elección:** Verde (Excelente), Amarillo (Regular), Rojo (Malo)
**Justificación:**
- Empleados de planta prefieren simplicidad
- Colores universales (semáforo)
- Evita paradoja de la elección (menos opciones = más engagement)
- Gamificación más clara
---
### ¿Por qué Export Excel (vs Dashboard online)?
**Ambos fueron implementados**, pero Excel es crítico para:
**Compliance regulatorio:**
- Auditores piden "archivo firmado digitalmente"
- NR-13/NR-20 exigen evidencia física
- Procesos laborales aceptan Excel
**Flexibilidad:**
- RRHH puede hacer análisis personalizados en Excel
- Combinar con otras fuentes de datos
- Presentaciones para dirección
---
## Lecciones Aprendidas
### 1. Solution Design Previene Retrabajo
**Lección:** 1 semana de análisis crítico economiza meses de refacción.
**Aplicación:**
- Siempre cuestionar especificaciones incompletas
- Pensar en el "día siguiente" (¿quién gestiona esto en producción?)
- Involucrar cliente en discusiones de requisitos
---
### 2. UX ≠ Requisitos Funcionales
**Lección:** Wireframes bonitos no sustituyen análisis de requisitos.
**UX se enfoca en:** Cómo usuario **usa** el sistema
**Solution Design se enfoca en:** Cómo sistema **funciona** end-to-end
Ambos son necesarios y complementarios.
---
### 3. Preguntar "¿Cómo?" es Más Importante que "¿Qué?"
**Cliente dice:** "Quiero rutas y microlearnings"
**Solution Designer pregunta:** "¿Cómo entra la primera ruta al sistema?"
Esta pregunta simple reveló 4 módulos faltantes.
---
### 4. User Stories Bien Escritas Aceleran Desarrollo
**Inversión:** 2 semanas escribiendo 32 user stories detalladas
**Retorno:**
- Devs sabían exactamente qué construir
- Zero ambigüedad
- Muy pocos bugs (requisitos claros)
- Cliente validó historias antes de codificar
**Lección:** Tiempo gastado en planificación reduce tiempo de desarrollo.
---
### 5. Compliance es Requisito Oculto
**En industrias reguladas** (salud, energía, químico), siempre habrá:
- Necesidad de auditoría
- Exportación de evidencias
- Logs de quién hizo qué
**Lección:** Preguntar sobre compliance **antes**, no después.
---
## Desafíos Superados
| Desafío | Solución | Resultado |
|---------|---------|-----------|
| Especificación incompleta | Análisis crítico preventa | Alcance correcto desde inicio |
| Cliente sin conocimiento técnico | User stories en lenguaje de negocio | Cliente validó requisitos |
| Empleados con baja familiaridad digital | UX simplificado (3 botones, colores) | 87% tasa de conclusión |
| Compliance NR-13/NR-20 | Export Excel con detalle | Aprobado en 2 auditorías |
| Gestión de 500+ usuarios | Import CSV + jerarquía de admins | Onboarding en 1 semana |
---
## Próximos Pasos (Roadmap Futuro)
**Funcionalidades planificadas:**
1. **Notificaciones Push**
- Recordar empleado de capacitación pendiente
- Avisar de nueva ruta obligatoria
2. **App Mobile Nativo**
- Offline-first (videos descargados)
- Empleados sin computadora
3. **Certificados Digitales**
- PDF firmado digitalmente
- QR code para validación
4. **Inteligencia de Datos**
- ¿Qué microlearnings tienen más error?
- Identificar gaps de conocimiento por área
---
**Resultado:** Sistema funcional en producción, cliente satisfecho, zero retrabajo - todo porque 1 semana fue invertida en **pensar antes de codificar**.
[¿Necesita análisis crítico de requisitos? Póngase en contacto](#contact)

View File

@ -0,0 +1,577 @@
---
title: "MVP Digital para Laboratorio Farmacéutico - De Cero a Producción"
slug: "pharma-digital-transformation"
summary: "Liderazgo de squad en proyecto greenfield para laboratorio farmacéutico, construyendo MVP de plataforma digital con integraciones complejas (Salesforce, Twilio, APIs oficiales) partiendo de cero absoluto - sin Git, sin servidores, sin infraestructura."
client: "Laboratorio Farmacéutico"
industry: "Farmacéutica & Salud"
timeline: "4 meses (2 meses de retraso planificado)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- MVP
- Digital Transformation
- .NET
- React
- Next.js
- Salesforce
- Twilio
- SQL Server
- Tech Lead
- Greenfield
featured: true
order: 3
date: 2023-03-01
seo_title: "MVP Digital Farmacéutico - Transformación Digital de Cero"
seo_description: "Caso de construcción de MVP digital para laboratorio farmacéutico partiendo de cero: sin Git, sin infraestructura, con integraciones complejas y entrega exitosa."
seo_keywords: "MVP, digital transformation, pharma, .NET, React, Next.js, Salesforce, greenfield project, tech lead"
---
## Descripción General
Laboratorio farmacéutico en el **inicio de transformación digital** contrata consultoría para construir plataforma de descuentos para médicos prescriptores, partiendo de prototipo en WordPress.
**Desafío único:** Comenzar proyecto greenfield en empresa **sin infraestructura básica** de desarrollo - sin Git, sin servidores aprovisionados, sin procesos definidos.
**Contexto:** Proyecto ejecutado en ambiente de múltiples squads. **Entrega exitosa en producción** a pesar de los desafíos iniciales de infraestructura, con retraso controlado de 2 meses.
---
## Desafío
### Transformación Digital... Partiendo de Cero Absoluto
**Estado inicial de la empresa (2023):**
❌ **Sin Git/versionamiento**
- Código solo en máquinas locales
- Histórico inexistente
- Colaboración imposible
❌ **Sin servidores aprovisionados**
- Ambiente de desarrollo inexistente
- Homologación no configurada
- Producción no preparada
❌ **Sin procesos de desarrollo**
- Sin CI/CD
- Sin code review
- Sin gestión de tareas estructurada
❌ **Sin equipo técnico interno experimentado**
- Equipo sin familiaridad con stacks modernos
- Primer contacto con React, APIs REST
- Inexperiencia con integraciones complejas
**Punto de partida técnico:**
- Prototipo funcional en **WordPress**
- Contenido y textos ya aprobados
- UX/UI definido
- Reglas de negocio documentadas (parcialmente)
---
### Integraciones Complejas Requeridas
El MVP necesitaba integrar con múltiples sistemas externos:
1. 🔐 **Salesforce** - Registro de pedidos de descuento
2. 📱 **Twilio** - SMS para validación de login (2FA)
3. 🏥 **API oficial de médicos** - Validación de CRM + datos profesionales
4. 🎯 **Interplayers** - Envío de registros de descuento por CPF
5. 📄 **WordPress** - Lectura de contenido (CMS headless)
6. 💾 **SQL Server** - Persistencia de datos
**Complejidad adicional:**
- Diferentes credenciales/ambientes por integración
- SLAs variados (Twilio crítico, WordPress tolerante)
- Tratamiento de errores específico por provider
- Compliance LGPD (datos sensibles de médicos)
---
## Arquitectura de Solución
### Estrategia: Start Small, Build Solid
**Decisión inicial:** Explicar al equipo el proceso que seguiríamos, estableciendo fundaciones antes de codificar.
### Fase 1: Setup de Infraestructura Básica (Semanas 1-2)
Incluso sin servidores aprovisionados, inicié setup esencial:
**Git & Versionamiento:**
```bash
# Repositorio estructurado desde día 1
git init
git flow init # Branch strategy definida
# Estructura de monorepo
/
├── frontend/ # Next.js + React
├── backend/ # .NET APIs
├── cms-adapter/ # WordPress integration
└── docs/ # Arquitectura y ADRs
```
**Proceso explicado al equipo:**
1. ✅ Todo en Git (commits atómicos, mensajes descriptivos)
2. ✅ Feature branches (nunca commit directo en main)
3. ✅ Code review obligatorio (2 aprobaciones)
4. ✅ CI/CD preparado (para cuando servidores estén listos)
**Ambientes locales primero:**
- Docker Compose para desarrollo local
- Mock de APIs externas (hasta que lleguen credenciales)
- SQL Server local con seeds de datos
---
### Fase 2: Arquitectura Moderna & Desacoplada
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND (Next.js + React) │
│ - SSR para SEO │
│ - Client-side para interactividad │
│ - Consumo de APIs │
└────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ BACKEND APIs (.NET 7) │
│ - REST APIs │
│ - Authentication/Authorization │
│ - Business logic │
│ - Orchestration layer │
└────┬────┬────┬────┬────┬─────────────────────────┬──┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│Salesf. │ │Twilio│ │CRM │ │Interpl.│ │WordPr. │ │SQL Server│
│ │ │ │ │API │ │ │ │(CMS) │ │ │
└────────┘ └──────┘ └──────┘ └────────┘ └────────┘ └──────────┘
```
**Stack elegido:**
**Frontend:**
- `Next.js 13` - SSR, routing, optimizaciones
- `React 18` - Componentes, hooks, context
- `TypeScript` - Type safety
- `Tailwind CSS` - Styling moderno
**Backend:**
- `.NET 7` - APIs REST
- `Entity Framework Core` - ORM
- `SQL Server 2019` - Base de datos
- `Polly` - Resilience patterns (retry, circuit breaker)
**¿Por qué Next.js en vez de mantener WordPress?**
- ✅ Performance (SSR vs PHP monolítico)
- ✅ SEO optimizado (crítico para farmacéutica)
- ✅ Experiencia moderna (SPA cuando necesario)
- ✅ Escalabilidad
- ✅ WordPress mantenido solo como CMS (headless)
---
### Fase 3: Integraciones (Core del Proyecto)
#### 1. Salesforce - Campañas y Registro de Pedidos
**Solución implementada:**
Salesforce fue configurado para gestionar dos funcionalidades principales:
**a) Campañas de descuento:**
- Marketing configura campañas en Salesforce (medicamento X, descuento Y%, período)
- Backend consulta campañas activas vía API
- Frontend (Next.js) muestra porcentaje de descuento disponible basado en: medicamento + campaña activa
**b) Registro de pedidos:**
- Usuario informa: CRM del médico, UF, CPF del paciente, medicamento
- Sistema valida datos (CRM real vía API oficial, CPF válido)
- Porcentaje es calculado automáticamente (campañas de Salesforce + reglas del CMS)
- Pedido es registrado en Salesforce con todos los datos (compliance LGPD)
**Desafíos técnicos superados:**
- Autenticación OAuth2 con refresh token automático
- Rate limiting (Salesforce tiene límites de API/día)
- Retry logic para fallas transitorias (Polly)
- Enmascaramiento de CPF para logs (LGPD)
---
#### 2. Twilio - Autenticación por SMS (2FA)
**Solución implementada:**
Sistema de autenticación de dos factores para garantizar seguridad:
**Flujo de login:**
1. Usuario informa teléfono
2. Backend genera código de 6 dígitos (válido por 5 minutos)
3. SMS enviado vía Twilio ("Su código: 123456")
4. Usuario digita código en frontend
5. Backend valida código + timestamp de expiración
6. Token JWT emitido tras validación exitosa
**Compliance y auditoría:**
- Teléfonos enmascarados en logs (LGPD)
- Auditoría completa (quién, cuándo, qué SMS)
- Tasa de entrega: 99.8%
---
#### 3. API Oficial de Médicos (Consejo Regional de Medicina)
**Solución implementada:**
Validación automática de médicos vía API oficial de los consejos de medicina:
**Validaciones realizadas:**
- CRM existe y está activo en el consejo
- Nombre del médico corresponde al CRM informado
- Especialidad es permitida (regla de negocio del laboratorio)
- UF corresponde al estado de registro
**Optimizaciones:**
- Cache de 24 horas para reducir llamadas a API oficial
- Fallback en caso de API fuera del aire (notifica admin)
- Retry automático para fallas transitorias
**Por qué esto importa:**
Garantiza que solo médicos reales y activos puedan prescribir descuentos, evitando fraudes.
---
#### 4. WordPress como CMS Headless
**Solución implementada:**
Marketing continúa gestionando contenido en WordPress (familiar), pero frontend es Next.js moderno.
**Arquitectura:**
- WordPress: Gestiona textos, imágenes, reglas de campañas
- WordPress REST API: Expone contenido vía JSON
- Next.js: Consume API y renderiza con SSR (SEO optimizado)
**Beneficios:**
- ✅ Marketing no necesita aprender nueva herramienta
- ✅ Frontend moderno (performance, UX)
- ✅ SEO optimizado (Server-Side Rendering)
- ✅ Separación clara de responsabilidades (contenido vs código)
---
### Fase 4: Resiliencia & Error Handling
Con múltiples integraciones externas, fallas son inevitables. La solución fue implementar **patrones de resiliencia** usando biblioteca Polly (.NET):
**Patrones implementados:**
**1. Retry (Reintentar)**
- Si Salesforce/Twilio/CRM API fallan, sistema intenta automáticamente 2-3x
- Espera crece exponencialmente (1s, 2s, 4s) para evitar sobrecarga
- Solo errores transitorios (timeout, 503) son reintentados
**2. Circuit Breaker (Disyuntor)**
- Si servicio falla 5x seguidas, "abre el circuito" por 30s
- Durante 30s, no intenta más (evita desperdiciar recursos)
- Tras 30s, intenta nuevamente (puede haber vuelto)
**3. Timeout**
- Cada integración tiene tiempo máximo de respuesta
- Evita requisiciones trabadas indefinidamente
**4. Fallback (Plan B)**
- Salesforce fuera: Pedido va a cola, procesa después
- Twilio fuera: Alerta administrador vía email
- CRM API fuera: Usa cache (datos de 24h atrás)
- WordPress fuera: Muestra contenido estático precargado
**Estrategias por integración:**
| Integración | Retry | Circuit Breaker | Timeout | Plan B |
|----------|-------|-----------------|---------|----------|
| Salesforce | 3x (exponencial) | 5 fallas/30s | 10s | Cola de retry |
| Twilio | 2x (lineal) | 3 fallas/60s | 5s | Alerta admin |
| CRM API | 3x (exponencial) | No | 15s | Cache |
| WordPress | No | No | 3s | Contenido estático |
**Resultado en producción:**
- Salesforce tuvo mantenimiento (1h) → Sistema continuó funcionando (cola procesó después)
- Twilio tuvo inestabilidad → Retry automático resolvió 95% de los casos
- Zero downtime percibido por los usuarios
---
## Superando Desafíos de Infraestructura
### Problema: Servidores No Aprovisionados
**Solución temporal:**
1. Desarrollo 100% local (Docker Compose)
2. Mocks de servicios externos (cuando credenciales se retrasaron)
3. CI/CD preparado pero no activo (esperando infra)
**Cuando llegaron servidores (semana 6):**
- Deploy en 2 horas (ya estaba preparado)
- Zero sorpresas (todo probado localmente)
- Rollout suave
---
### Problema: Credenciales de Integración Retrasadas
**Impacto:** Twilio y Salesforce demoraron 3 semanas en ser aprovisionadas.
**Solución:** Crear versiones "mock" (simuladas) de cada integración:
- Mock de Twilio: Registra en log en vez de enviar SMS real
- Mock de Salesforce: Guarda pedido en archivo JSON local
- Mock de CRM API: Retorna datos ficticios de médicos
**Cómo funciona:**
- Ambiente de desarrollo: Usa mocks (no necesita credenciales)
- Ambiente de producción: Usa integraciones reales (cuando lleguen credenciales)
- Cambio automático basado en configuración
**Resultado:** Equipo continuó 100% productivo durante 3 semanas, probando flujos completos sin depender de credenciales.
---
### Problema: Equipo Inexperto con Stack Moderno
**Contexto:** Equipo no tenía experiencia con React, TypeScript, .NET Core moderno, APIs REST.
**Abordaje de capacitación:**
**1. Pair Programming (1h/día por desarrollador)**
- Tech lead trabaja al lado del dev
- Compartir pantalla + explicación en tiempo real
- Dev escribe código, tech lead guía
**2. Code Review Educativo**
- No solo "aprobar" o "reprobar"
- Comentarios explican el **por qué** de cada sugerencia
- Ejemplo: "Siempre trate errores de requisiciones! Si API cae, usuario necesita saber qué pasó."
**3. Documentación Viva**
- ADRs (Architecture Decision Records): ¿Por qué elegimos X y no Y?
- READMEs: Cómo ejecutar, cómo probar, cómo deployar
- Onboarding guide: De cero a primera feature
**4. Live Coding Semanal (2h)**
- Tech lead resuelve problema real en vivo
- Equipo observa proceso de pensamiento
- Q&A al final
**Resultado:**
- Tras 4 semanas, equipo estaba autónomo
- Calidad de código aumentó consistentemente
- Devs pasaron a hacer code review entre sí (peer review)
---
## Resultados e Impacto
### Entrega Exitosa A Pesar de los Desafíos
**Contexto:** Programa con múltiples squads trabajando en paralelo.
**Resultado alcanzado:**
- ✅ **MVP entregado en producción con éxito**
- ✅ Retraso controlado de 2 meses (significativamente menor que otras iniciativas del programa)
- ✅ Todas las integraciones funcionando según planificado
- ✅ Zero critical bugs en producción (primera semana)
**¿Por qué la entrega fue exitosa?**
1. **Setup anticipado** - Git, procesos, Docker local desde día 1
2. **Mocks estratégicos** - Equipo no quedó bloqueado esperando infra
3. **Arquitectura sólida** - Resiliencia desde el inicio
4. **Upskilling continuo** - Equipo aprendió haciendo
5. **Comunicación proactiva** - Riesgos reportados temprano
---
### Métricas del MVP
**Performance:**
- ⚡ Tiempo de carga: <2s (95th percentile)
- 📱 Lighthouse score: 95+ (mobile)
- 🔒 SSL A+ rating
**Integraciones:**
- 📊 Salesforce: 100% de pedidos sincronizados
- 📱 Twilio: 99.8% delivery rate
- 🏥 CRM API: 10k validaciones/día (media)
- 💾 SQL Server: 50k registros/mes
**Adopción:**
- 👨‍⚕️ 2.000+ médicos registrados (3 primeros meses)
- 🎯 15.000+ pedidos de descuento procesados
- ⭐ 4.8/5 satisfacción (encuesta interna)
---
### Impacto en el Cliente
**Transformación digital iniciada:**
- ✅ Git implementado y adoptado
- ✅ Procesos de desarrollo establecidos
- ✅ Equipo interno capacitado en stacks modernos
- ✅ Infraestructura cloud configurada (Azure)
- ✅ Roadmap de evolución definido
**Base para futuros proyectos:**
- Arquitectura sirvió de referencia para otras iniciativas
- Patrones de código documentados (coding standards)
- Pipelines CI/CD reutilizados
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React 18` `Next.js 13` `TypeScript` `Tailwind CSS` `Salesforce API` `Twilio` `WordPress REST API` `Docker` `Polly` `OAuth2` `JWT` `LGPD Compliance`
---
## Decisiones Clave & Trade-offs
### ¿Por qué Next.js en vez de React puro?
**Requisitos:**
- SEO crítico (farmacéutica necesita ranquear)
- Performance (médicos usan mobile)
- Contenido dinámico (WordPress)
**Next.js ofrece:**
- ✅ SSR out-of-the-box
- ✅ API routes (BFF pattern)
- ✅ Optimizaciones automáticas (image, fonts)
- ✅ Deploy simplificado (Vercel, Azure)
---
### ¿Por qué mantener WordPress?
**Alternativas consideradas:**
1. ❌ Migrar contenido a BD + CMS custom (tiempo)
2. ❌ Strapi/Contentful (costos + learning curve)
3. ✅ **WordPress headless** (mejor trade-off)
**Ventajas:**
- Equipo de marketing ya sabe usar
- Contenido aprobado ya estaba ahí
- WordPress REST API es sólida
- Costo cero (ya estaba ejecutándose)
---
### ¿Por qué .NET 7 en vez de Node.js?
**Contexto:** Cliente tenía preferencia por stack Microsoft.
**Beneficios adicionales:**
- Performance superior (vs Node en APIs)
- Type safety nativa (C#)
- Entity Framework (ORM maduro)
- Integración fácil con Azure (deploy futuro)
- Equipo del cliente tenía familiaridad
---
## Lecciones Aprendidas
### 1. Infraestructura Retrasada? Prepare Alternativas
No espere servidores/credenciales para comenzar:
- Docker local es su amigo
- Mocks permiten progreso
- CI/CD puede ser preparado antes de tener dónde deployar
**Lección:** Controle lo que puede controlar.
---
### 2. Procesos > Herramientas
Incluso sin Git corporativo, establecí:
- Branching strategy
- Code review
- Commit conventions
- Documentation standards
**Resultado:** Cuando llegaron herramientas, equipo ya sabía usarlas.
---
### 3. Upskilling es Inversión, No Costo
Pair programming y code reviews tomaron tiempo, pero:
- ✅ Equipo quedó autónomo más rápido
- ✅ Calidad de código aumentó
- ✅ Knowledge sharing natural
- ✅ Onboarding de nuevos devs simplificado
---
### 4. Resiliencia Desde el Inicio
Implementar Polly (retry, circuit breaker) al inicio salvó en producción:
- Twilio tuvo inestabilidad (resuelta automáticamente)
- Salesforce tuvo mantenimiento (queue funcionó)
- CRM API tuvo lentitud (cache mitigó)
**Lección:** No deje resiliencia para "después". Fallas van a ocurrir.
---
### 5. Comunicación Clara de Riesgos
Reporté semanalmente:
- Bloqueos (infraestructura, credenciales)
- Riesgos (plazos, dependencias)
- Soluciones alternativas (mocks, workarounds)
**Resultado:** Stakeholders sabían exactamente el estado y no tuvieron sorpresas.
---
## Desafíos & Cómo Fueron Superados
| Desafío | Impacto | Solución | Resultado |
|---------|---------|---------|-----------|
| Sin Git | Bloqueo total | Setup local + GitLab Cloud | Equipo productivo día 1 |
| Sin servidores | Sin ambiente de dev | Docker Compose local | Dev/test local completo |
| Credenciales retrasadas | Integración bloqueada | Mock services | Progreso sin bloqueo |
| Equipo inexperto | Código de baja calidad | Pair prog + Code review | Ramp-up en 4 semanas |
| Múltiples integraciones | Complejidad alta | Polly + patterns | Zero downtime prod |
---
## Próximos Pasos (Post-MVP)
**Roadmap sugerido al cliente:**
1. **Fase 2: Expansión de funcionalidades**
- Dashboard para médicos (histórico de pedidos)
- Notificaciones push (Firebase)
- Integración con e-commerce (compra directa)
2. **Fase 3: Optimizaciones**
- Cache distribuido (Redis)
- CDN para assets estáticos
- Analytics avanzado (Amplitude)
3. **Fase 4: Escala**
- Kubernetes (AKS)
- Microservicios (quebrar monolito)
- Event-driven architecture (Azure Service Bus)
---
**Resultado:** MVP entregado en producción a pesar de comenzar literalmente de cero, estableciendo fundaciones sólidas para transformación digital del cliente.
[¿Necesita construir un MVP en escenario desafiante? Póngase en contacto](#contact)

View File

@ -0,0 +1,211 @@
---
title: "Sistema de Integración SAP Healthcare"
slug: "sap-integration-healthcare"
summary: "Integración bidireccional procesando 100k+ transacciones/día con 99.9% uptime"
client: "Confidencial - Multinacional Healthcare"
industry: "Healthcare"
timeline: "6 meses"
role: "Arquitecto de Integración"
image: ""
tags:
- SAP
- C#
- .NET
- Integraciones
- Enterprise
- Healthcare
featured: true
order: 1
date: 2023-06-15
seo_title: "Caso: Integración SAP Healthcare - 100k Transacciones/Día"
seo_description: "Cómo arquitectamos sistema de integración SAP procesando 100k+ transacciones diarias con 99.9% uptime para empresa healthcare."
seo_keywords: "integración SAP, C#, .NET, SAP Connector, enterprise integration, healthcare"
---
## Descripción General
**Cliente:** Multinacional Healthcare (confidencial)
**Tamaño:** 100.000+ empleados
**Proyecto:** Integración de beneficios
**Timeline:** 6 meses
**Mi Rol:** Arquitecto de Integración
---
## Desafío
El cliente tenía sistema interno de gestión de beneficios que necesitaba sincronizar con SAP ECC para procesar nómina.
### Dolores principales:
- Proceso manual sujeto a errores
- 3-5 días de delay entre sistemas
- 100k empleados esperando procesamiento
- Picos de carga (fin de mes)
### Constraints:
- Presupuesto limitado (sin SAP BTP)
- Equipo SAP interno pequeño (2 desarrolladores)
- Plazo ajustado (6 meses go-live)
- Sistema legacy .NET Framework 4.5
---
## Solución
Arquitectura de integración bidireccional:
```
[Sistema Interno] ←→ [Queue] ←→ [SAP Connector] ←→ [SAP ECC]
↓ ↓
[MongoDB Logs] [ABAP Z_BENEFITS]
```
### Componentes:
- .NET Service con SAP Connector (NCo 3.0)
- ABAP transaction personalizada (Z_BENEFITS)
- Queue system (RabbitMQ) para retry logic
- MongoDB para auditoría y troubleshooting
- Scheduler (Hangfire) para batch processing
### Flujo:
1. Sistema genera cambios (new hire, alteraciones)
2. Service procesa batch (500 registros/vez)
3. SAP Connector llama Z_BENEFITS vía RFC
4. SAP retorna estado (éxito/error)
5. Retry automático si falla (máx 3x)
6. Logs MongoDB para troubleshooting
---
## Resultado
### Métricas:
- **100k+** transacciones/día procesadas
- **99.9%** uptime
- Reducción de **5 días → 4 horas** (delay)
- **80%** reducción tiempo procesamiento
- **Zero** errores manuales (vs 2-3% antes)
### Beneficios:
- Empleados reciben beneficios a tiempo
- Equipo RRHH economiza 40h/mes (trabajo manual)
- Auditoría completa (compliance)
- Escalable (crecimiento 30% año sin refactor)
---
## Tech Stack
`C#` `.NET Framework 4.5` `SAP NCo 3.0` `RabbitMQ` `MongoDB` `Hangfire` `Docker` `SAP ECC` `ABAP` `RFC`
---
## Decisiones & Motivación
### 💡 Decisión 1: SAP Connector vs SAP BTP
**Opciones evaluadas:**
- SAP BTP (eventos, APIs modernas, cloud)
- SAP Connector (RFC directo, on-premise)
**Elegimos:** SAP Connector
**Motivación:**
- Cliente tenía SAP ECC on-premise (no S/4)
- Presupuesto no permitía licencia BTP
- Equipo SAP cómodo con ABAP/RFC
- Necesidades atendidas con RFC (no necesitaba event-driven real-time)
**Trade-off aceptado:**
- Menos "moderno" que BTP, pero 100% funcional
- Costo $0 adicional vs $30k+/año BTP
- Delivery 2 meses más rápido (sin learning curve BTP)
---
### 💡 Decisión 2: Queue System vs Llamadas Directas
**Opciones evaluadas:**
- Llamadas síncronas directas (más simple)
- Queue con retry (más complejo)
**Elegimos:** Queue + Retry
**Motivación:**
- SAP ocasionalmente indisponible (mantenimiento)
- Picos de carga (fin de mes = 200k reqs)
- Garantizar zero pérdida de datos
- Resiliencia > simplicidad (ambiente crítico)
**Implementación:**
- RabbitMQ con dead-letter queue
- Retry exponencial (1min, 5min, 15min)
- Alertas si 3 fallas consecutivas
**Resultado:**
- Zero pérdida datos en 2 años producción
- Equipo RRHH no necesita "estar pendiente"
---
### 💡 Decisión 3: ABAP Personalizado vs Standard
**Opciones evaluadas:**
- BAPIs standard SAP (zero código ABAP)
- Transaction personalizada (Z_BENEFITS)
**Elegimos:** Transaction personalizada
**Motivación:**
- BAPIs standard no tenían validaciones específicas del negocio
- Cliente quería lógica centralizada en SAP (single source of truth)
- Permitió validaciones complejas (elegibilidad, dependientes, límites)
**Trade-off:**
- Requiere mantenimiento ABAP (equipo SAP interno)
- Pero: Cliente prefirió vs lógica dual (riesgo desincronización)
---
### ❌ Alternativas NO Elegidas
**Webhook/Callback (Event-Driven):**
- Cliente no tenía infraestructura exponer APIs
- Sistema interno detrás de firewall
- Polling batch funciona bien para el caso
**Microservicios Kubernetes:**
- Overkill para integración única
- Equipo no tenía expertise K8s
- Docker simple suficiente
**Real-time Sync (<1min):**
- Negocio no necesita (batch diario ok)
- Costo infra aumentaría 3x
- 4h delay es aceptable para nómina
---
## Aprendizajes
### ✅ Lo que funcionó muy bien:
- Involucrar equipo SAP desde día 1 (buy-in)
- MongoDB para logs (troubleshooting 10x más rápido)
- Retry logic salvó innumerables veces
### 🔄 Lo que haría diferente:
- Agregar health check endpoint más temprano
- Dashboard de monitoreo desde inicio (agregamos después)
### 📚 Lecciones para próximos proyectos:
- Cliente "presupuesto limitado" ≠ "solución limitada" - creatividad resuelve
- Documentar TODAS decisiones arquitecturales (team turnover)
- Simplicidad vence complejidad cuando ambas funcionan (KISS)
---
## ¿Necesita Algo Similar?
Integraciones SAP complejas, sistemas legacy, o arquitectura de alta disponibilidad?
[Conversemos sobre su desafío →](/#contact)

View File

@ -0,0 +1,329 @@
---
title: "Migração ASP 3.0 para .NET Core - Sistema de Rastreamento de Cargas"
slug: "asp-to-dotnet-migration"
summary: "Tech Lead na migração gradual de sistema crítico ASP 3.0 para .NET Core, com sincronização de dados entre versões e redução de custos de R$ 100k/ano em APIs de mapeamento."
client: "Empresa de Logística e Rastreamento"
industry: "Logística & Segurança"
timeline: "12 meses (migração completa)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- ASP Classic
- .NET Core
- SQL Server
- Migration
- Tech Lead
- OSRM
- APIs
- Arquitetura
featured: true
order: 2
date: 2015-06-01
seo_title: "Migração ASP 3.0 para .NET Core - Case Carneiro Tech"
seo_description: "Case de migração gradual de aplicação ASP 3.0 para .NET Core com sincronização de dados e redução de R$ 100k/ano em custos de APIs."
seo_keywords: "ASP migration, .NET Core, legacy modernization, SQL Server, OSRM, tech lead, routing API"
---
## Overview
Sistema crítico de monitoramento de cargas de alto valor (TVs LED de R$ 3.000 cada, carregamentos de até 1000 unidades) usando rastreamento GPS via satélite. A aplicação cobria todo o ciclo: desde cadastro e avaliação de motoristas (background check policial) até monitoramento em tempo real e entrega final.
**Desafio principal:** Migrar aplicação ASP 3.0 legada para .NET Core sem downtime, mantendo operação crítica 24/7.
---
## Challenge
### Sistema Legado Crítico
A empresa operava um sistema mission-critical em **ASP 3.0** (Classic ASP) que não podia parar:
**Tecnologia legada:**
- ASP 3.0 (tecnologia de 1998)
- SQL Server 2005
- Cluster failover on-premises (perfeitamente capaz de suportar a carga)
- Integração com rastreadores GPS via satélite
- Google Maps API (custo: **R$ 100.000/ano** apenas para cálculo de rotas)
**Restrições:**
- Sistema operando 24/7 com cargas de alto valor
- Impossibilidade de downtime durante migração
- Múltiplos módulos interdependentes
- Equipe precisava continuar desenvolvendo features durante a migração
---
## Solution Architecture
### Fase 1: Preparação da Infraestrutura (Meses 1-3)
#### Upgrade do Banco de Dados
```
SQL Server 2005 → SQL Server 2014
- Backup completo e validação
- Migração de stored procedures
- Otimização de índices
- Testes de performance
```
#### Estratégia de Sincronização Dual-Write
Implementei um **sistema de sincronização bidirecional** que permitia:
1. **Módulos novos (.NET Core)** gravavam no banco novo
2. **Trigger automático** sincronizava dados para o banco legado
3. **Módulos antigos (ASP 3.0)** continuavam funcionando normalmente
4. **Zero downtime** durante toda a migração
```csharp
// Exemplo de sincronização implementada
public class DualWriteService
{
public async Task SaveDriver(Driver driver)
{
// Grava no banco novo (.NET Core)
await _newDbContext.Drivers.AddAsync(driver);
await _newDbContext.SaveChangesAsync();
// Trigger SQL sincroniza automaticamente para banco legado
// Módulos ASP 3.0 continuam funcionando
}
}
```
**Por que essa abordagem?**
- Permitiu migração **módulo por módulo**
- Equipe podia continuar desenvolvendo
- Rollback simples se necessário
- Redução de risco operacional
---
### Fase 2: Migração Gradual dos Módulos (Meses 4-12)
Migrei os módulos em ordem de complexidade crescente:
**Ordem de migração:**
1. ✅ Cadastros básicos (motoristas, veículos)
2. ✅ Avaliação de risco (integração com base policial)
3. ✅ Gestão de cargas e rotas
4. ✅ Monitoramento GPS em tempo real
5. ✅ Alertas e notificações
6. ✅ Relatórios e analytics
**Stack da aplicação migrada:**
- `.NET Core 1.0` (2015-2016 era o início do .NET Core)
- `Entity Framework Core`
- `SignalR` para monitoramento real-time
- `SQL Server 2014`
- APIs RESTful
---
### Fase 3: Redução de Custos com OSRM (Economia de R$ 100k/ano)
#### Problema: Custo Proibitivo do Google Maps
A empresa gastava **R$ 100.000/ano** apenas com Google Maps Directions API para cálculo de rotas dos caminhões.
#### Solução: OSRM (Open Source Routing Machine)
Implementei uma solução baseada em **OSRM** (motor de roteamento open-source):
**Arquitetura da solução:**
```
┌─────────────────┐
│ Frontend │
│ (Leaflet.js) │
└────────┬────────┘
┌─────────────────┐ ┌──────────────┐
│ API Wrapper │─────▶│ OSRM Server │
│ (.NET Core) │ │ (self-hosted)│
└────────┬────────┘ └──────────────┘
┌─────────────────┐
│ Google Maps │
│ (display only) │
└─────────────────┘
```
**Implementação:**
1. **OSRM Server configurado** em servidor próprio
2. **API wrapper amigável** em .NET Core que:
- Recebia origem/destino
- Consultava OSRM (gratuito)
- Retornava todos os pontos da rota
- Formatava para o frontend
3. **Frontend** desenhava a rota no Google Maps (apenas visualização, sem API de rotas)
```csharp
[HttpGet("route")]
public async Task<IActionResult> GetRoute(double originLat, double originLng,
double destLat, double destLng)
{
// Consulta OSRM (gratuito)
var osrmResponse = await _osrmClient.GetRouteAsync(
originLat, originLng, destLat, destLng);
// Retorna pontos formatados para o frontend
return Ok(new {
points = osrmResponse.Routes[0].Geometry.Coordinates,
distance = osrmResponse.Routes[0].Distance,
duration = osrmResponse.Routes[0].Duration
});
}
```
**Frontend com Leaflet:**
```javascript
// Desenha rota no mapa (Google Maps apenas para tiles)
L.polyline(routePoints, {color: 'red'}).addTo(map);
```
#### Tentativa com OpenStreetMap
Tentei substituir também o Google Maps (tiles) por **OpenStreetMap**, que funcionou tecnicamente, mas:
**Usuários não gostaram** da aparência
❌ Preferiam a interface familiar do Google Maps
**Decisão:** Manter Google Maps apenas para visualização (sem custo de API de rotas)
**Resultado:** Economia de **~R$ 100.000/ano** mantendo qualidade das rotas.
---
## Results & Impact
### Migração Completa em 12 Meses
**100% dos módulos** migrados de ASP 3.0 para .NET Core
**Zero downtime** durante toda a migração
**Equipe produtiva** durante todo o processo
✅ Sistema mais rápido e escalável
### Redução de Custos
💰 **R$ 100.000/ano economizados** com substituição do Google Maps Directions API
📉 **Infraestrutura otimizada** com SQL Server 2014
### Melhorias Técnicas
🚀 **Performance:** Aplicação .NET Core 3x mais rápida que ASP 3.0
🔒 **Segurança:** Stack moderna com patches de segurança ativos
🛠️ **Manutenibilidade:** Código C# moderno vs VBScript legado
📊 **Monitoramento:** SignalR para tracking real-time mais eficiente
---
## Fase Não Executada: Microserviços & Cloud
### Planejamento Inicial
Participei do **design e concepção** da segunda fase (nunca executada):
**Arquitetura planejada:**
- Migração para **Azure** (cloud estava apenas começando em 2015)
- Quebra em **microserviços**:
- Serviço de autenticação
- Serviço de GPS/tracking
- Serviço de rotas
- Serviço de notificações
- **Event-driven architecture** com message queues
**Por que não foi executada:**
Saí da empresa logo após concluir a migração para .NET Core. A segunda fase ficou planejada mas não foi implementada por mim.
---
## Tech Stack
`ASP 3.0` `VBScript` `.NET Core 1.0` `C#` `Entity Framework Core` `SQL Server 2005` `SQL Server 2014` `OSRM` `Leaflet.js` `Google Maps` `SignalR` `REST APIs` `GPS/Satellite` `Migration Strategy` `Dual-Write Pattern`
---
## Key Decisions & Trade-offs
### Por que sincronização dual-write?
**Alternativas consideradas:**
1. ❌ Big Bang migration (muito arriscado)
2. ❌ Manter tudo em ASP 3.0 (insustentável)
3. ✅ **Migração gradual com sync** (escolhido)
**Justificativa:**
- Sistema crítico não podia parar
- Permitiu rollback módulo por módulo
- Equipe continuou produtiva
### Por que OSRM em vez de outros?
**Alternativas:**
- Google Maps: R$ 100k/ano ❌
- Mapbox: Licença paga ❌
- GraphHopper: Configuração complexa ❌
- **OSRM: Open-source, rápido, configurável**
### Por que não OpenStreetMap para tiles?
**Decisão baseada em UX:**
- Tecnicamente funcionou perfeitamente
- Usuários preferiam interface familiar do Google
- **Compromisso:** Google Maps para visualização (grátis) + OSRM para rotas (grátis)
---
## Lessons Learned
### 1. Migração Gradual > Big Bang
Migrar módulo por módulo com sincronização permitiu:
- Aprendizado contínuo
- Ajustes de rota durante o processo
- Confiança da equipe e stakeholders
### 2. Open Source Pode Economizar Muito
OSRM economizou **R$ 100k/ano** sem perda de qualidade. Mas requer:
- Expertise para configurar
- Infraestrutura própria
- Manutenção contínua
### 3. UX > Tecnologia às Vezes
OpenStreetMap era tecnicamente superior (gratuito), mas usuários preferiram Google Maps. **Lição:** Ouvir os usuários finais.
### 4. Planeje Cloud, mas Valide o ROI
Em 2015, cloud estava começando. A infraestrutura on-premises (cluster SQL Server) era perfeitamente capaz. **Não force cloud se não há benefício claro.**
---
## Context: Por que 2015 foi um Momento Especial?
**Estado da tecnologia em 2015:**
- ☁️ **Cloud engatinhando:** AWS existia, Azure crescendo, mas adoção corporativa ainda baixa
- 🆕 **.NET Core 1.0 lançado** em junho/2016 (usamos RC durante projeto)
- 📱 **Microserviços:** Conceito novo, Docker em adoção inicial
- 🗺️ **Google Maps dominante:** APIs pagas, poucas alternativas open-source maduras
**Desafios da época:**
- Ferramentas de migração ASP→.NET inexistentes
- Documentação .NET Core escassa (versão 1.0!)
- Padrões de arquitetura ainda se consolidando
Este projeto foi **pioneiro** ao adotar .NET Core logo no início, quando a maioria migrava para .NET Framework 4.x.
---
**Resultado:** Migração bem-sucedida de sistema crítico 24/7, economia de R$ 100k/ano, e base sólida para evolução futura.
[Quer discutir uma migração similar? Entre em contato](#contact)

View File

@ -0,0 +1,382 @@
---
title: "CNPJ Fast - Processo de Migração para CNPJ Alfanumérico"
slug: "cnpj-fast-process"
summary: "Criação de metodologia estruturada para migração de aplicações ao novo formato de CNPJ alfanumérico brasileiro, vendida para seguradora e empresa de cobrança."
client: "Empresa de Consultoria (Interno)"
industry: "Consultoria & Transformação Digital"
timeline: "3 meses (criação do processo)"
role: "Solution Architect & Process Designer"
image: ""
tags:
- Process Design
- CNPJ
- Migration Strategy
- Regulatory Compliance
- Consulting
- Sales Enablement
featured: true
order: 3
date: 2024-09-01
seo_title: "CNPJ Fast - Metodologia de Migração CNPJ Alfanumérico"
seo_description: "Case de criação de processo estruturado para migração ao CNPJ alfanumérico brasileiro, vendido para seguradora e empresa de cobrança."
seo_keywords: "CNPJ alfanumérico, migration process, regulatory compliance, consulting, methodology"
---
## Overview
Com a introdução do **CNPJ alfanumérico** pela Receita Federal brasileira, empresas enfrentavam o desafio de adaptar suas aplicações legadas que armazenavam CNPJ como campos numéricos (`bigint`, `numeric`, `int`).
Criei o **CNPJ Fast**, uma metodologia estruturada para avaliar, planejar e executar migrações de CNPJ em aplicações e bancos de dados corporativos.
**Resultado:** Processo vendido para **2 clientes** (seguradora e empresa de cobrança) antes mesmo da implementação.
---
## Challenge
### Mudança Regulatória Complexa
**Contexto regulatório:**
- Receita Federal brasileira introduziu **CNPJ alfanumérico**
- CNPJ deixa de ser apenas números (14 dígitos)
- Passa a aceitar **letras e números** (formato alfanumérico)
**Impacto nas empresas:**
```sql
-- ANTES: CNPJ numérico
CNPJ BIGINT -- 12345678000190
-- DEPOIS: CNPJ alfanumérico
CNPJ VARCHAR(18) -- 12.ABC.678/0001-90
```
**Problemas identificados:**
1. 🗄️ **Banco de dados:** Colunas `BIGINT`, `NUMERIC`, `INT` não suportam caracteres
2. 🔑 **Chaves primárias:** CNPJ usado como PK em várias tabelas
3. 🔗 **Foreign keys:** Relacionamentos entre tabelas
4. 📊 **Volume:** Milhões de registros para migrar
5. 💻 **Aplicações:** Validações, máscaras, regras de negócio
6. 🧪 **Testes:** Garantir integridade após migração
7. ⏱️ **Downtime:** Janelas de manutenção limitadas
**Sem um processo estruturado**, empresas arriscavam:
- Perda de dados
- Inconsistências no banco
- Aplicações quebradas
- Downtime prolongado
---
## Solution: CNPJ Fast Process
### Metodologia em 5 Fases
Desenhei um processo estruturado que poderia ser replicado em diferentes clientes:
```
┌─────────────────────────────────────────────┐
│ FASE 1: DISCOVERY & ASSESSMENT │
│ - Inventário de aplicações │
│ - Análise de schemas de banco │
│ - Identificação de tabelas impactadas │
│ - Estimativa de volume de dados │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 2: IMPACT ANALYSIS │
│ - Mapeamento de dependências │
│ - Análise de chaves primárias/estrangeiras │
│ - Identificação de regras de negócio │
│ - Avaliação de risco │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 3: MIGRATION PLANNING │
│ - Estratégia de migração (phased commits) │
│ - Scripts SQL automatizados │
│ - Plano de rollback │
│ - Janelas de manutenção │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 4: EXECUTION │
│ - Migração de dados em lotes │
│ - Atualização de aplicações │
│ - Testes de integração │
│ - Validação de integridade │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ FASE 5: VALIDATION & GO-LIVE │
│ - Testes de regressão │
│ - Validação de performance │
│ - Go-live coordenado │
│ - Monitoramento pós-migração │
└─────────────────────────────────────────────┘
```
---
### Fase 1: Discovery & Assessment
**Objetivo:** Entender o escopo completo da migração
**Entregáveis:**
1. **Inventário de Aplicações**
- Lista de aplicações que usam CNPJ
- Tecnologias (ASP 3.0, VB6, .NET, microserviços)
- Criticidade de cada aplicação
2. **Análise de Schema**
```sql
-- Script de descoberta automática
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
ON t.TABLE_NAME = c.TABLE_NAME
WHERE c.COLUMN_NAME LIKE '%CNPJ%'
AND c.DATA_TYPE IN ('bigint', 'numeric', 'int')
ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME;
```
3. **Estimativa de Volume**
- Total de registros por tabela
- Tamanho em GB
- Tempo estimado de migração
**Exemplo de output:**
| Tabela | Coluna | Tipo Atual | Registros | Criticidade |
|--------|--------|------------|-----------|-------------|
| Clientes | CNPJ_Cliente | BIGINT | 8.000.000 | Alta |
| Fornecedores | CNPJ_Fornecedor | NUMERIC(14) | 2.500.000 | Média |
| Transações | CNPJ_Pagador | BIGINT | 90.000.000 | Crítica |
---
### Fase 2: Impact Analysis
**Objetivo:** Mapear todas as dependências e riscos
**Análise de chaves:**
```sql
-- Identifica PKs e FKs envolvendo CNPJ
SELECT
fk.name AS FK_Name,
tp.name AS Parent_Table,
cp.name AS Parent_Column,
tr.name AS Referenced_Table,
cr.name AS Referenced_Column
FROM sys.foreign_keys fk
INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id
INNER JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id
AND fkc.parent_object_id = cp.object_id
INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id
INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id
AND fkc.referenced_object_id = cr.object_id
WHERE cp.name LIKE '%CNPJ%' OR cr.name LIKE '%CNPJ%';
```
**Avaliação de Risco:**
- 🔴 **Alto:** Tabelas com CNPJ como PK e >10M registros
- 🟡 **Médio:** Tabelas com FK para CNPJ
- 🟢 **Baixo:** Tabelas sem constraints
---
### Fase 3: Migration Planning
**Estratégia de migração gradual:**
Para evitar travamento de banco, desenhei estratégia de **phased commits**:
```sql
-- Estratégia para tabelas grandes (>1M registros)
-- 1. Adicionar nova coluna VARCHAR
ALTER TABLE Clientes
ADD CNPJ_Cliente_New VARCHAR(18) NULL;
-- 2. Migração em lotes (commits faseados)
DECLARE @BatchSize INT = 100000;
DECLARE @RowsAffected INT = 1;
WHILE @RowsAffected > 0
BEGIN
UPDATE TOP (@BatchSize) Clientes
SET CNPJ_Cliente_New = FORMAT(CNPJ_Cliente, '00000000000000')
WHERE CNPJ_Cliente_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
WAITFOR DELAY '00:00:01'; -- Pausa entre lotes
END;
-- 3. Remover constraints (PKs, FKs)
ALTER TABLE Clientes DROP CONSTRAINT PK_Clientes;
-- 4. Renomear colunas
EXEC sp_rename 'Clientes.CNPJ_Cliente', 'CNPJ_Cliente_Old', 'COLUMN';
EXEC sp_rename 'Clientes.CNPJ_Cliente_New', 'CNPJ_Cliente', 'COLUMN';
-- 5. Recriar constraints
ALTER TABLE Clientes
ADD CONSTRAINT PK_Clientes PRIMARY KEY (CNPJ_Cliente);
-- 6. Remover coluna antiga (após validação)
ALTER TABLE Clientes DROP COLUMN CNPJ_Cliente_Old;
```
**Por que essa abordagem?**
- ✅ Evita lock de tabela inteira
- ✅ Permite pausar/retomar migração
- ✅ Minimiza impacto em produção
- ✅ Facilita rollback se necessário
---
### Fase 4 & 5: Execution e Validation
**Checklist de execução:**
- [ ] Backup completo do banco
- [ ] Executar scripts de migração em lotes
- [ ] Atualizar aplicações (validações, máscaras)
- [ ] Testes de integração
- [ ] Validação de integridade referencial
- [ ] Testes de performance
- [ ] Go-live coordenado
- [ ] Monitoramento 24h pós-migração
---
## Sales Enablement: Apresentação UX
**Colaboração com Gestor de UX:**
O gestor de UX da empresa criou uma **apresentação visual impactante** do processo CNPJ Fast:
**Conteúdo da apresentação:**
- 📊 Infográficos do processo de 5 fases
- 📈 Exemplos de estimativas de tempo/custo
- 🎯 Casos de uso (seguradoras, bancos, fintechs)
- ✅ Checklist executivo
- 📋 Templates de documentação
**Resultado:** Apresentação usada pelo time comercial para prospecção.
---
## Results & Impact
### Vendas Realizadas
**Cliente 1: Seguradora**
- Stack: ASP 3.0, VB6 components, .NET, microserviços
- Escopo: Migração completa de aplicações legadas
- Status: **Projeto vendido** (execução por outra equipe)
- Valor: [Confidencial]
**Cliente 2: Empresa de Cobrança**
- Escopo: Migração de banco de dados (~100M registros)
- Status: **Projeto vendido e em execução** (por mim)
- Particularidade: Processo foi **reestruturado** para atender necessidades específicas
- Ver case completo: [Migração CNPJ - 100M Registros](/cases/cnpj-migration-database)
---
### Impacto no Negócio
💰 **2 projetos vendidos** antes mesmo da primeira execução
📈 **Processo replicável** para novos clientes
🎯 **Posicionamento** como especialista em migrações regulatórias
📚 **Base de conhecimento** para futuros projetos similares
---
### Impacto Técnico
🔧 **Metodologia testada** em cenários reais
📖 **Documentação reutilizável** (scripts, checklists, templates)
🚀 **Aceleração** de projetos similares (de semanas para dias)
---
## Tech Stack
`SQL Server` `Migration Strategy` `Process Design` `Regulatory Compliance` `ASP 3.0` `VB6` `.NET` `Microservices` `Batch Processing` `Database Optimization`
---
## Key Decisions & Trade-offs
### Por que processo estruturado?
**Alternativas:**
1. ❌ Abordagem ad-hoc por projeto
2. ❌ Consultoria manual sem metodologia
3. ✅ **Processo replicável e escalável**
**Justificativa:**
- Reduz tempo de Discovery
- Padroniza entregas
- Facilita vendas (apresentação pronta)
- Permite execução por diferentes equipes
### Por que separar em 5 fases?
**Benefícios:**
- Cliente pode aprovar fase a fase
- Permite ajustes durante o processo
- Facilita gestão de riscos
- Entregas incrementais
---
## Lessons Learned
### 1. UX/Apresentação Importa para Vendas
A apresentação visual feita pelo gestor de UX foi **crucial** para fechar os 2 contratos. Processo técnico bom + apresentação ruim = sem vendas.
### 2. Processo Vende, Não Apenas Execução
Criar uma **metodologia documentada** tem mais valor comercial do que apenas oferecer "horas de consultoria".
### 3. Cada Cliente é Único
O cliente pediu **reestruturação do processo**. Um bom processo deve ser:
- Estruturado o suficiente para ser replicável
- Flexível o suficiente para customizar
### 4. Colaboração Multidisciplinar
Trabalhar com o gestor de UX (apresentações) + time comercial (vendas) + técnico (execução) = sucesso.
---
## Next Steps
**Oportunidades futuras:**
1. 🌎 **Expansão:** Oferecer CNPJ Fast para mais setores (bancos, fintechs, varejo)
2. 📦 **Produto:** Transformar em ferramenta automatizada (SaaS)
3. 📚 **Treinamento:** Capacitar equipes internas de clientes
4. 🔄 **Evolução:** Adaptar processo para outras migrações regulatórias (PIX, Open Banking)
---
**Resultado:** Metodologia estruturada que virou produto vendável, gerando receita antes mesmo da primeira execução técnica.
[Quer implementar CNPJ Fast na sua empresa? Entre em contato](#contact)

View File

@ -0,0 +1,469 @@
---
title: "Migração CNPJ Alfanumérico - 100 Milhões de Registros"
slug: "cnpj-migration-database"
summary: "Execução de migração massiva de CNPJ numérico para alfanumérico em banco de dados com ~100M registros, usando estratégia de commits faseados para evitar travamento."
client: "Empresa de Cobrança"
industry: "Cobrança & Serviços Financeiros"
timeline: "Em execução"
role: "Database Architect & Tech Lead"
image: ""
tags:
- SQL Server
- Database Migration
- CNPJ
- Performance Optimization
- Batch Processing
- Big Data
featured: true
order: 4
date: 2024-11-01
seo_title: "Migração CNPJ Alfanumérico - 100M Registros | Carneiro Tech"
seo_description: "Case de migração massiva de CNPJ em banco de dados com 100 milhões de registros usando commits faseados e otimizações de performance."
seo_keywords: "database migration, SQL Server, CNPJ, batch processing, performance optimization, phased commits"
---
## Overview
Uma empresa de cobrança que trabalha com bancos de dados de informação transitória (sem software proprietário) precisa adaptar seus sistemas ao novo formato de **CNPJ alfanumérico** brasileiro.
**Desafio principal:** Migrar ~**100 milhões de registros** em tabelas com colunas `BIGINT` e `NUMERIC` para `VARCHAR`, sem travar o banco de dados em produção.
**Status:** Projeto em execução (preparação de scripts de migração).
---
## Challenge
### Volume Massivo de Dados
**Contexto da empresa:**
- Empresa de cobrança (não desenvolve software próprio)
- Trabalha com **dados transitórios** (alta rotatividade)
- Banco de dados SQL Server com volume crítico
**Análise inicial revelou:**
| Tabela | Coluna | Tipo Atual | Registros | Tamanho |
|--------|--------|------------|-----------|---------|
| Devedores | CNPJ_Devedor | BIGINT | 8.000.000 | 60 GB |
| Transações | CNPJ_Pagador | NUMERIC(14) | 90.000.000 | 1.2 TB |
| Empresas | CNPJ_Empresa | BIGINT | 2.500.000 | 18 GB |
| **TOTAL** | - | - | **~100.000.000** | **~1.3 TB** |
**Problemas identificados:**
1. 🔴 **Tabelas com 8M+ linhas** usando `BIGINT` para CNPJ
2. 🔴 **90 milhões de registros** em tabela de transações
3. 🔑 **CNPJ como chave primária** em algumas tabelas
4. 🔗 **Foreign keys** relacionando múltiplas tabelas
5. ⚠️ **Impossibilidade de downtime prolongado** (operação 24/7)
6. 💾 **Restrições de espaço** em disco (precisa estratégia eficiente)
---
## Strategic Decision: Phased Commits
### Por que NÃO fazer ALTER COLUMN direto?
**Abordagem ingênua (NÃO funciona):**
```sql
-- ❌ NUNCA FAÇA ISSO EM TABELAS GRANDES
ALTER TABLE Transacoes
ALTER COLUMN CNPJ_Pagador VARCHAR(18);
```
**Problemas:**
- 🔒 Trava a tabela inteira durante a conversão
- ⏱️ Pode levar horas/dias em tabelas grandes
- 💥 Bloqueia todas as operações (INSERT, UPDATE, SELECT)
- 🚨 Risco de timeout ou falha no meio da operação
- 🔙 Rollback complexo se algo der errado
---
### Estratégia Escolhida: Column Swap com Commits Faseados
**Baseado em experiência anterior**, decidi usar abordagem gradual:
```
┌─────────────────────────────────────────────┐
│ 1. Criar nova coluna VARCHAR no FINAL │
│ (operação rápida, não bloqueia tabela) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 2. UPDATE em lotes (commits faseados) │
│ - 100k registros por vez │
│ - Pausa entre lotes (evita contenção) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 3. Remover PKs e FKs │
│ (após 100% migrado) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 4. Renomear colunas (swap) │
│ - CNPJ → CNPJ_Old │
│ - CNPJ_New → CNPJ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 5. Recriar PKs/FKs com nova coluna │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 6. Validação e exclusão da coluna antiga │
└─────────────────────────────────────────────┘
```
**Por que essa abordagem?**
**Sem lock de tabela completa** (operação incremental)
**Pode pausar/retomar** a qualquer momento
**Monitoramento de progresso** em tempo real
**Rollback simples** (basta dropar nova coluna)
**Minimiza impacto em produção** (commits pequenos)
**Decisão tomada baseada em:**
- 📚 Experiência anterior com migrações de grande volume
- 🔍 Conhecimento de locks do SQL Server
- 🎯 Necessidade de zero downtime
**Nota:** Essa decisão foi tomada **sem consultar IA** - baseada puramente em experiência prática de projetos anteriores.
---
## Implementation Details
### Fase 1: Criar Nova Coluna
```sql
-- Operação rápida (metadata change apenas)
ALTER TABLE Transacoes
ADD CNPJ_Pagador_New VARCHAR(18) NULL;
-- Adiciona índice temporário para acelerar lookups
CREATE NONCLUSTERED INDEX IX_Temp_CNPJ_New
ON Transacoes(CNPJ_Pagador_New)
WHERE CNPJ_Pagador_New IS NULL;
```
**Tempo estimado:** ~1 segundo (independente do tamanho da tabela)
---
### Fase 2: Migração em Lotes (Core Strategy)
```sql
-- Script de migração com commits faseados
DECLARE @BatchSize INT = 100000; -- 100k registros por lote
DECLARE @RowsAffected INT = 1;
DECLARE @TotalProcessed INT = 0;
DECLARE @StartTime DATETIME = GETDATE();
WHILE @RowsAffected > 0
BEGIN
BEGIN TRANSACTION;
-- Atualiza lote de 100k registros ainda não migrados
UPDATE TOP (@BatchSize) Transacoes
SET CNPJ_Pagador_New = RIGHT('00000000000000' + CAST(CNPJ_Pagador AS VARCHAR), 14)
WHERE CNPJ_Pagador_New IS NULL;
SET @RowsAffected = @@ROWCOUNT;
SET @TotalProcessed = @TotalProcessed + @RowsAffected;
COMMIT TRANSACTION;
-- Log de progresso
PRINT 'Processed: ' + CAST(@TotalProcessed AS VARCHAR) + ' rows. Batch: ' + CAST(@RowsAffected AS VARCHAR);
PRINT 'Elapsed time: ' + CAST(DATEDIFF(SECOND, @StartTime, GETDATE()) AS VARCHAR) + ' seconds';
-- Pausa entre lotes (reduz contenção)
WAITFOR DELAY '00:00:01'; -- 1 segundo entre lotes
END;
PRINT 'Migration completed! Total rows: ' + CAST(@TotalProcessed AS VARCHAR);
```
**Parâmetros configuráveis:**
- `@BatchSize`: 100k (balanceado entre performance e lock time)
- Muito pequeno = muitas transações, overhead
- Muito grande = lock prolongado, impacto em prod
- `WAITFOR DELAY`: 1 segundo (dá tempo de outras queries rodarem)
**Estimativas de tempo:**
| Registros | Batch Size | Tempo Estimado |
|-----------|------------|----------------|
| 8.000.000 | 100.000 | ~2-3 horas |
| 90.000.000 | 100.000 | ~20-24 horas |
**Vantagens:**
- ✅ Não trava aplicação
- ✅ Outras queries conseguem rodar entre os lotes
- ✅ Pode pausar (Ctrl+C) e retomar depois (WHERE NULL pega de onde parou)
- ✅ Log de progresso em tempo real
---
### Fase 3: Remoção de Constraints
```sql
-- Identifica todas as PKs e FKs envolvendo a coluna
SELECT name
FROM sys.key_constraints
WHERE type = 'PK'
AND parent_object_id = OBJECT_ID('Transacoes')
AND COL_NAME(parent_object_id, parent_column_id) = 'CNPJ_Pagador';
-- Remove PKs
ALTER TABLE Transacoes
DROP CONSTRAINT PK_Transacoes_CNPJ;
-- Remove FKs (tabelas que referenciam)
ALTER TABLE Pagamentos
DROP CONSTRAINT FK_Pagamentos_Transacoes;
```
**Tempo estimado:** ~10 minutos (depende de quantas constraints existem)
---
### Fase 4: Column Swap (Renomeação)
```sql
-- Renomeia coluna antiga para _Old
EXEC sp_rename 'Transacoes.CNPJ_Pagador', 'CNPJ_Pagador_Old', 'COLUMN';
-- Renomeia nova coluna para o nome original
EXEC sp_rename 'Transacoes.CNPJ_Pagador_New', 'CNPJ_Pagador', 'COLUMN';
-- Altera para NOT NULL (após validação de 100% preenchido)
ALTER TABLE Transacoes
ALTER COLUMN CNPJ_Pagador VARCHAR(18) NOT NULL;
```
**Tempo estimado:** ~1 segundo (metadata change)
---
### Fase 5: Recriação de Constraints
```sql
-- Recria PK com nova coluna VARCHAR
ALTER TABLE Transacoes
ADD CONSTRAINT PK_Transacoes_CNPJ
PRIMARY KEY CLUSTERED (CNPJ_Pagador);
-- Recria FKs
ALTER TABLE Pagamentos
ADD CONSTRAINT FK_Pagamentos_Transacoes
FOREIGN KEY (CNPJ_Pagador) REFERENCES Transacoes(CNPJ_Pagador);
```
**Tempo estimado:** ~30-60 minutos (depende do volume)
---
### Fase 6: Validação e Limpeza
```sql
-- Valida que 100% foi migrado
SELECT COUNT(*)
FROM Transacoes
WHERE CNPJ_Pagador IS NULL OR CNPJ_Pagador = '';
-- Valida integridade referencial
DBCC CHECKCONSTRAINTS WITH ALL_CONSTRAINTS;
-- Se tudo OK, remove coluna antiga
ALTER TABLE Transacoes
DROP COLUMN CNPJ_Pagador_Old;
-- Remove índice temporário
DROP INDEX IX_Temp_CNPJ_New ON Transacoes;
```
---
## Customização do Processo CNPJ Fast
### Diferenças vs. Processo Original
O processo **CNPJ Fast** original foi **reestruturado** para este cliente:
**Mudanças principais:**
| Aspecto | CNPJ Fast Original | Cliente (Customizado) |
|---------|-------------------|---------------------|
| **Foco** | Aplicações + DB | Apenas DB (sem software próprio) |
| **Discovery** | Inventário de apps | Apenas análise de schema |
| **Execução** | Múltiplas aplicações | Scripts SQL massivos |
| **Batch Size** | 50k-100k | 100k (otimizado para volume) |
| **Monitoramento** | Manual + ferramentas | Logs SQL em tempo real |
| **Rollback** | Processo complexo | Simples (DROP COLUMN) |
**Motivo da reestruturação:**
- Cliente não tem aplicações próprias (apenas consome dados)
- Foco 100% em otimização de banco
- Volume muito maior que casos típicos (100M vs ~10M)
---
## Tech Stack
`SQL Server` `T-SQL` `Batch Processing` `Performance Tuning` `Database Optimization` `Migration Scripts` `Phased Commits` `Index Optimization` `Constraint Management`
---
## Key Decisions & Trade-offs
### Por que 100k por batch?
**Testes de performance:**
| Batch Size | Tempo/Batch | Lock Duration | Contenção |
|------------|-------------|---------------|-----------|
| 10.000 | 2s | Baixo | ✅ Mínimo |
| 50.000 | 8s | Médio | ✅ Aceitável |
| **100.000** | 15s | **Médio** | **✅ Balanceado** |
| 500.000 | 90s | Alto | ❌ Impacto em prod |
| 1.000.000 | 180s | Muito alto | ❌ Inaceitável |
**Escolha:** 100k oferece melhor balanço entre performance e impacto.
---
### Por que criar coluna no FINAL?
**SQL Server internals:**
- Adicionar coluna no final = metadata change (rápido)
- Adicionar no meio = reescrita de páginas (lento)
- Para tabelas grandes, posição importa
---
### Por que WAITFOR DELAY de 1 segundo?
**Sem delay:**
- ❌ Batch processing consome 100% do I/O
- ❌ Queries de aplicação ficam lentas
- ❌ Lock escalation pode ocorrer
**Com delay de 1s:**
- ✅ Outras queries têm janela para executar
- ✅ I/O distribuído
- ✅ Experiência do usuário preservada
**Trade-off:** Migração leva +1s por batch (~25% mais lenta), mas sistema permanece responsivo.
---
## Current Status & Next Steps
### Status Atual (Dezembro 2024)
📝 **Fase de Preparação:**
- ✅ Discovery completo (100M registros identificados)
- ✅ Scripts de migração desenvolvidos
- ✅ Testes em ambiente de homologação
- 🔄 Validação de performance
- ⏳ Aguardando janela de manutenção para produção
### Próximos Passos
1. **Backup completo** de produção
2. **Execução em produção** (ambiente 24/7)
3. **Monitoramento em tempo real** durante migração
4. **Validação pós-migração** (integridade, performance)
5. **Documentação de lessons learned**
---
## Lessons Learned (Até Agora)
### 1. Experiência Anterior Vale Ouro
Decisão de usar phased commits veio de **experiência prática** em projetos anteriores, não de documentação ou IA.
**Situações similares anteriores:**
- Migração de dados em e-commerce (50M registros)
- Conversão de encoding (UTF-8 em 100M+ rows)
- Particionamento de tabelas históricas
---
### 2. "Measure Twice, Cut Once"
Antes de executar em produção:
- ✅ Testes exaustivos em homologação
- ✅ Scripts validados e revisados
- ✅ Rollback testado
- ✅ Estimativas de tempo confirmadas
**Tempo de preparação:** 3 semanas
**Tempo de execução:** Estimado em 48 horas
**Ratio:** 10:1 (preparação vs execução)
---
### 3. Customização > One-Size-Fits-All
O processo CNPJ Fast original precisou ser **reestruturado** para este cliente.
**Lição:** Processos devem ser:
- Estruturados o suficiente para repetir
- Flexíveis o suficiente para adaptar
---
### 4. Monitoramento é Crucial
Scripts com **log detalhado** de progresso permitem:
- Estimar tempo restante
- Identificar gargalos
- Pausar/retomar com confiança
- Reportar status para stakeholders
```sql
-- Log example
Processed: 10.000.000 rows. Batch: 100.000
Elapsed time: 3600 seconds (10% complete, ~9h remaining)
```
---
## Performance Optimizations
### Otimizações Implementadas
1. **Índice temporário WHERE NULL**
- Acelera lookup de registros não migrados
- Removido após conclusão
2. **Batch size otimizado**
- Balanceado entre performance e lock time
3. **Transaction log management**
```sql
-- Verificar crescimento do log
DBCC SQLPERF(LOGSPACE);
-- Ajustar recovery model (se permitido)
ALTER DATABASE MyDatabase SET RECOVERY SIMPLE;
```
4. **Execução em horário de menor carga**
- Janela de manutenção noturna
- Final de semana (se possível)
---
**Resultado esperado:** Migração de 100 milhões de registros em ~48 horas, sem downtime significativo e com possibilidade de rollback rápido.
[Precisa migrar volumes massivos de dados? Entre em contato](#contact)

View File

@ -0,0 +1,588 @@
---
title: "Plataforma de Treinamento Industrial - De Wireframes a Sistema Completo"
slug: "industrial-learning-platform"
summary: "Solution Design para plataforma de microlearning em empresa de gases industriais. Identificação de requisitos críticos não mapeados (admin, cadastros, exportação) antes da apresentação ao cliente, evitando retrabalho e garantindo usabilidade real."
client: "Empresa de Gases Industriais"
industry: "Industrial & Manufatura"
timeline: "4 meses"
role: "Solution Architect & Tech Lead"
image: ""
tags:
- Solution Design
- EdTech
- Learning Platform
- Requirements Analysis
- Tech Lead
- User Stories
- .NET
- Product Design
featured: true
order: 5
date: 2024-06-01
seo_title: "Plataforma de Treinamento Industrial - Solution Design"
seo_description: "Case de Solution Design para plataforma de microlearning, identificando requisitos críticos antes da apresentação ao cliente e liderando desenvolvimento até produção."
seo_keywords: "solution design, learning platform, microlearning, requirements analysis, tech lead, industrial training"
---
## Overview
Empresa de gases industriais solicita plataforma para treinar funcionários usando metodologia de **microlearning** (conteúdos curtos e objetivos).
**Requisito inicial:** "Queremos apenas a estrutura - trilha, microlearning, pergunta de teste e pontuação."
**Problema:** Especificação incompleta que resultaria em sistema **impossível de usar** (sem forma de cadastrar conteúdo, sem administradores, sem exportar resultados).
**Solução:** Análise crítica de requisitos **antes da apresentação ao cliente**, identificando gaps funcionais e propondo solução completa.
---
## Challenge
### Wireframes Bonitos, Funcionalidade Incompleta
**Situação inicial:**
UX criou wireframes lindos mostrando:
- ✅ Trilhas de aprendizado
- ✅ Microlearnings (vídeo/texto + imagem)
- ✅ Perguntas de teste (múltipla escolha)
- ✅ Pontuação por funcionário
**Problema identificado:**
Ninguém (cliente, UX, comercial) pensou em:
❌ **Como conteúdo entra no sistema?**
- Quem cadastra trilhas?
- Quem cria microlearnings?
- Quem escreve perguntas?
- Interface manual ou import?
❌ **Quem gerencia o sistema?**
- Existe conceito de admin?
- RH pode criar admins?
- Gestor de área pode ver apenas seu time?
❌ **Como dados saem do sistema?**
- RH precisa de relatórios
- Compliance precisa de evidências
- Como exportar dados?
- Formato: Excel? PDF? API?
**Risco real:**
Se desenvolvêssemos exatamente o que foi pedido:
- Sistema funcionaria tecnicamente ✅
- **Mas seria completamente inutilizável**
- Cliente teria que pagar refação para incluir CRUD básico
- Retrabalho + custo adicional + frustração
---
## Solution Design Process
### Etapa 1: Análise Crítica (Antes da Apresentação)
**Ação tomada:** Chamei reunião com UX **antes** de apresentar ao cliente.
**Pontos levantados:**
**"Como o primeiro conteúdo entra no sistema?"**
- UX: "Ah... não pensamos nisso. Vocês vão popular o banco?"
- Eu: "E quando cliente quiser adicionar nova trilha? Vamos alterar banco em produção?"
**"Quem é o dono do sistema?"**
- UX: "O RH, imagino."
- Eu: "Apenas uma pessoa? E se ela sair da empresa? Como ela delega?"
**"RH pediu relatórios?"**
- UX: "Não foi mencionado no briefing."
- Eu: "RH sempre precisa de relatórios. É para compliance (NR, ISO)."
---
### Etapa 2: Requisitos Funcionais Identificados
Propus 4 módulos adicionais **essenciais**:
#### 1. Sistema de Administração
**Funcionalidades:**
- Usuário padrão: Apenas faz treinamentos
- Usuário admin: Gerencia conteúdo + vê relatórios
- Admin pode promover outros usuários a admin
- Controle de acesso (admin geral vs admin de área)
**Por que é crítico:**
Sem isso, sistema é estático (conteúdo nunca atualiza).
---
#### 2. CRUD de Conteúdo
**a) Cadastro de Trilhas:**
- Nome da trilha
- Descrição
- Ordem dos microlearnings
- Trilha ativa/inativa (permite despublicar)
**b) Cadastro de Microlearnings:**
- Título
- Tipo: Texto simples (2 parágrafos) OU Vídeo
- Upload de imagem (se texto)
- URL de vídeo (se vídeo)
- Ordem dentro da trilha
**c) Cadastro de Perguntas:**
- Pergunta (texto)
- 3 opções de resposta:
- "Ótimo" (verde)
- "Mais ou menos" (amarelo)
- "Ruim" (vermelho)
- Pontuação por resposta (ex: 10, 5, 0 pontos)
- Feedback customizado por resposta
**Por que é crítico:**
Cliente precisa atualizar conteúdo sem chamar dev/DBA.
---
#### 3. Exportação de Dados
**Funcionalidades:**
- Exportar para Excel (.xlsx)
- Filtros:
- Por período (data início/fim)
- Por trilha
- Por funcionário
- Por área/departamento
- Colunas exportadas:
- Nome do funcionário
- Matrícula
- Trilha concluída
- Pontuação total
- Data de conclusão
- Respostas individuais (para auditoria)
**Por que é crítico:**
RH precisa evidenciar treinamento para:
- Normas Regulamentadoras (NR-13, NR-20 - gases inflamáveis)
- Auditorias ISO
- Processos trabalhistas
---
#### 4. Gestão de Usuários
**Funcionalidades:**
- Importar funcionários (upload CSV/Excel)
- Cadastro manual
- Ativar/desativar usuários
- Atribuir trilhas obrigatórias por área
- Notificações de pendências
**Por que é crítico:**
Empresa tem 500+ funcionários, cadastro manual é inviável.
---
### Etapa 3: Apresentação ao Cliente
**Abordagem:**
1. Mostrei wireframes do UX (interface bonita)
2. Perguntei: "Como vocês vão cadastrar a primeira trilha?"
3. Cliente: "Ah... boa pergunta. Não tínhamos pensado nisso."
4. Apresentei os 4 módulos adicionais
5. Cliente: "Faz total sentido! Sem isso não conseguimos usar."
**Resultado:**
- Proposta aprovada **com módulos adicionais**
- Escopo ajustado (timeline + orçamento)
- Zero retrabalho futuro
- Cliente reconheceu valor agregado
---
## Implementation
### Meu Papel no Projeto
**1. Solution Architect**
- Identificação de requisitos não-funcionais
- Desenho de arquitetura (módulos, integrações)
- Definição de tecnologias
**2. Tech Lead**
- Liderança técnica da equipe (3 devs)
- Code review
- Definição de padrões de código
- Gestão de riscos técnicos
**3. Product Owner Técnico**
- Criação de **user stories** completas
- Priorização de backlog
- Refinamento contínuo com cliente
---
### Stack Técnico Escolhido
**Backend:**
- `.NET 7` - APIs REST
- `Entity Framework Core` - ORM
- `SQL Server` - Banco de dados
- `ClosedXML` - Geração de Excel
**Frontend:**
- `React` - Interface web
- `Material-UI` - Componentes
- `React Player` - Player de vídeo
- `Chart.js` - Gráficos de progresso
**Infraestrutura:**
- `Azure App Service` - Hospedagem
- `Azure Blob Storage` - Armazenamento de vídeos/imagens
- `Azure SQL Database` - Banco gerenciado
---
### User Stories Criadas
Escrevi **32 user stories** cobrindo todos os fluxos. Exemplos:
**US-01: Cadastrar Trilha (Admin)**
```
Como administrador do sistema
Quero cadastrar uma nova trilha de treinamento
Para que funcionários possam realizar os cursos
Critérios de aceitação:
- Admin acessa menu "Trilhas" → "Nova Trilha"
- Preenche: Nome, Descrição, Status (Ativa/Inativa)
- Pode adicionar microlearnings existentes à trilha
- Define ordem dos microlearnings (drag & drop)
- Sistema valida campos obrigatórios
- Salva e exibe mensagem de sucesso
```
**US-15: Realizar Microlearning (Funcionário)**
```
Como funcionário
Quero realizar um microlearning da minha trilha
Para aprender sobre o tema e ganhar pontos
Critérios de aceitação:
- Funcionário acessa trilha atribuída
- Vê lista de microlearnings (não completados primeiro)
- Clica em microlearning → abre tela com:
- Texto (2 parágrafos) + Imagem OU
- Player de vídeo embarcado
- Botão "Continuar" aparece após:
- 30s (se texto)
- Final do vídeo (se vídeo)
- Marca microlearning como visto
- Pergunta de teste aparece automaticamente
```
**US-22: Exportar Resultados (Admin)**
```
Como administrador
Quero exportar resultados de treinamento para Excel
Para gerar relatórios de compliance e auditorias
Critérios de aceitação:
- Admin acessa "Relatórios" → "Exportar"
- Seleciona filtros (período, trilha, área)
- Clica "Gerar Excel"
- Sistema processa e baixa arquivo .xlsx
- Excel contém colunas: Nome, Matrícula, Trilha, Pontos, Data, Respostas
- Formato legível (headers em negrito, colunas autoajustadas)
```
---
## Key Features Implemented
### 1. Sistema de Pontuação Gamificado
**Mecânica:**
- Cada pergunta vale pontos (configurável)
- Resposta "Ótimo": 10 pontos
- Resposta "Mais ou menos": 5 pontos
- Resposta "Ruim": 0 pontos
**Dashboard do funcionário:**
- Pontuação total
- Ranking (opcional, configurável)
- Badges por trilhas concluídas
- Progresso visual (barra de %)
**Por que funciona:**
Funcionários de chão de fábrica engajam mais com elementos de gamificação.
---
### 2. Microlearning Adaptativo
**Tipos de conteúdo:**
**Texto + Imagem:**
- 2 parágrafos (máx 300 palavras)
- 1 imagem ilustrativa
- Ideal para: Procedimentos, normas, conceitos
**Vídeo:**
- Vídeos curtos (2-5 min)
- Player embarcado (YouTube/Vimeo ou upload)
- Ideal para: Demonstrações, operações de equipamento
**Por que microlearning?**
- Funcionários fazem no intervalo (10-15min)
- Conteúdo curto = maior retenção
- Facilita atualização (vs cursos longos)
---
### 3. Sistema de Administração Delegada
**Hierarquia:**
```
Admin Geral (RH)
↓ pode promover
Admin de Área (Gerentes)
↓ pode visualizar apenas
Funcionários da sua área
```
**Permissões:**
- Admin geral: Cria trilhas, promove admins, vê todos os dados
- Admin de área: Vê apenas relatórios da sua área
- Funcionário: Apenas realiza treinamentos
**Auditoria:**
- Logs de quem criou/editou cada conteúdo
- Histórico de promoções a admin
- Compliance SOX/ISO
---
### 4. Exportação para Compliance
**Formato do Excel gerado:**
| Matrícula | Nome | Área | Trilha | Data Conclusão | Pontos | Status |
|-----------|------|------|--------|----------------|--------|--------|
| 1001 | João Silva | Produção | Segurança NR-20 | 15/11/2024 | 95/100 | ✅ Aprovado |
| 1002 | Maria Santos | Logística | Manuseio Gases | 14/11/2024 | 78/100 | ✅ Aprovado |
**Aba adicional: Detalhamento de Respostas**
- Permite auditoria: "Funcionário X acertou pergunta Y?"
- Evidência para processos trabalhistas
- Compliance NR-13/NR-20
---
## Results & Impact
### Sistema em Produção
**Status atual:** Em uso há 4+ meses
**Métricas de adoção:**
- 👥 500+ funcionários cadastrados
- 📚 12 trilhas ativas
- 📖 150+ microlearnings criados
- ✅ 8.000+ treinamentos concluídos
- 📊 100+ relatórios exportados (compliance)
**Taxa de conclusão:** 87% (média indústria: 45%)
---
### Impacto no Cliente
**Antes:**
- Treinamentos presenciais (custo alto, agenda difícil)
- Evidências em papel (perdas, difícil auditoria)
- Dificuldade em atualizar conteúdo
**Depois:**
- Treinamento assíncrono (funcionário faz quando pode)
- Evidências digitais (compliance facilitado)
- RH atualiza conteúdo sem chamar TI
- Redução de 70% no custo de treinamento
**Feedback do cliente:**
> "Se tivéssemos implementado apenas o que pedimos inicialmente, o sistema seria inútil. A análise prévia salvou o projeto."
---
### Valor do Solution Design
**ROI da análise pré-venda:**
**Cenário A (sem análise):**
1. Desenvolver apenas interface (2 meses)
2. Cliente testa e percebe que falta CRUD (1 mês depois)
3. Refação para adicionar módulos (2+ meses)
4. **Total: 5+ meses + frustração do cliente**
**Cenário B (com análise - o que fizemos):**
1. Identificar requisitos antes (1 semana)
2. Aprovar escopo completo (1 semana)
3. Desenvolver solução correta (4 meses)
4. **Total: 4 meses + cliente satisfeito**
**Economia:** 1+ mês de retrabalho + custo de oportunidade
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React` `Material-UI` `Azure App Service` `Azure Blob Storage` `ClosedXML` `Chart.js` `User Stories` `Solution Design` `Tech Lead`
---
## Key Decisions & Trade-offs
### Por que não usar LMS pronto? (Moodle, Canvas)
**Alternativas consideradas:**
1. ❌ Moodle (open-source, gratuito)
2. ❌ Totara/Canvas (LMS corporativo)
3. ✅ **Desenvolvimento custom**
**Justificativa:**
- LMS genérico: Complexidade desnecessária (fóruns, wikis, etc)
- Cliente quer **apenas microlearning** (simplicidade)
- Custo de licença LMS > custo de dev custom
- Integração com AD/SSO do cliente (mais fácil custom)
- UX otimizada para chão de fábrica (mobile-first, touch)
---
### Por que 3 opções de resposta (vs 4-5)?
**Escolha:** Verde (Ótimo), Amarelo (Mais ou menos), Vermelho (Ruim)
**Justificativa:**
- Funcionários de chão de fábrica preferem simplicidade
- Cores universais (semáforo)
- Evita paradoxo da escolha (menos opções = mais engajamento)
- Gamificação mais clara
---
### Por que Export Excel (vs Dashboard online)?
**Ambos foram implementados**, mas Excel é crítico para:
**Compliance regulatório:**
- Auditores pedem "arquivo assinado digitalmente"
- NR-13/NR-20 exigem evidência física
- Processos trabalhistas aceitam Excel
**Flexibilidade:**
- RH pode fazer análises customizadas no Excel
- Combinar com outras fontes de dados
- Apresentações para diretoria
---
## Lessons Learned
### 1. Solution Design Previne Retrabalho
**Lição:** 1 semana de análise crítica economiza meses de refação.
**Aplicação:**
- Sempre questionar especificações incompletas
- Pensar no "dia seguinte" (quem gerencia isso em produção?)
- Envolver cliente em discussões de requisitos
---
### 2. UX ≠ Requisitos Funcionais
**Lição:** Wireframes bonitos não substituem análise de requisitos.
**UX foca em:** Como usuário **usa** o sistema
**Solution Design foca em:** Como sistema **funciona** end-to-end
Ambos são necessários e complementares.
---
### 3. Perguntar "Como?" é Mais Importante que "O Quê?"
**Cliente diz:** "Quero trilhas e microlearnings"
**Solution Designer pergunta:** "Como a primeira trilha entra no sistema?"
Essa pergunta simples revelou 4 módulos faltantes.
---
### 4. User Stories Bem Escritas Aceleram Desenvolvimento
**Investimento:** 2 semanas escrevendo 32 user stories detalhadas
**Retorno:**
- Devs sabiam exatamente o que construir
- Zero ambiguidade
- Pouquíssimos bugs (requisitos claros)
- Cliente validou histórias antes de codificar
**Lição:** Tempo gasto em planejamento reduz tempo de desenvolvimento.
---
### 5. Compliance é Requisito Oculto
**Em indústrias reguladas** (saúde, energia, químico), sempre haverá:
- Necessidade de auditoria
- Exportação de evidências
- Logs de quem fez o quê
**Lição:** Perguntar sobre compliance **antes**, não depois.
---
## Challenges Overcome
| Desafio | Solução | Resultado |
|---------|---------|-----------|
| Especificação incompleta | Análise crítica pré-venda | Escopo correto desde início |
| Cliente sem conhecimento técnico | User stories em linguagem de negócio | Cliente validou requisitos |
| Funcionários com baixa familiaridade digital | UX simplificado (3 botões, cores) | 87% taxa de conclusão |
| Compliance NR-13/NR-20 | Export Excel com detalhamento | Aprovado em 2 auditorias |
| Gestão de 500+ usuários | Import CSV + hierarquia de admins | Onboarding em 1 semana |
---
## Next Steps (Roadmap Futuro)
**Funcionalidades planejadas:**
1. **Notificações Push**
- Lembrar funcionário de treinamento pendente
- Avisar de nova trilha obrigatória
2. **App Mobile Nativo**
- Offline-first (vídeos baixados)
- Funcionários sem computador
3. **Certificados Digitais**
- PDF assinado digitalmente
- QR code para validação
4. **Inteligência de Dados**
- Quais microlearnings têm mais erro?
- Identificar gaps de conhecimento por área
---
**Resultado:** Sistema funcional em produção, cliente satisfeito, zero retrabalho - tudo porque 1 semana foi investida em **pensar antes de codificar**.
[Precisa de análise crítica de requisitos? Entre em contato](#contact)

View File

@ -0,0 +1,577 @@
---
title: "MVP Digital para Laboratório Farmacêutico - Do Zero à Produção"
slug: "pharma-digital-transformation"
summary: "Liderança de squad em projeto greenfield para laboratório farmacêutico, construindo MVP de plataforma digital com integrações complexas (Salesforce, Twilio, APIs oficiais) partindo do zero absoluto - sem Git, sem servidores, sem infraestrutura."
client: "Laboratório Farmacêutico"
industry: "Farmacêutica & Saúde"
timeline: "4 meses (2 meses de atraso planejado)"
role: "Tech Lead & Solution Architect"
image: ""
tags:
- MVP
- Digital Transformation
- .NET
- React
- Next.js
- Salesforce
- Twilio
- SQL Server
- Tech Lead
- Greenfield
featured: true
order: 3
date: 2023-03-01
seo_title: "MVP Digital Farmacêutico - Transformação Digital do Zero"
seo_description: "Case de construção de MVP digital para laboratório farmacêutico partindo do zero: sem Git, sem infraestrutura, com integrações complexas e entrega bem-sucedida."
seo_keywords: "MVP, digital transformation, pharma, .NET, React, Next.js, Salesforce, greenfield project, tech lead"
---
## Overview
Laboratório farmacêutico no **início da transformação digital** contrata consultoria para construir plataforma de descontos para médicos prescritores, partindo de protótipo em WordPress.
**Desafio único:** Começar projeto greenfield em empresa **sem infraestrutura básica** de desenvolvimento - sem Git, sem servidores provisionados, sem processos definidos.
**Contexto:** Projeto executado em ambiente de múltiplas squads. **Entrega bem-sucedida em produção** apesar dos desafios iniciais de infraestrutura, com atraso controlado de 2 meses.
---
## Challenge
### Transformação Digital... Partindo do Zero Absoluto
**Estado inicial da empresa (2023):**
❌ **Sem Git/versionamento**
- Código apenas em máquinas locais
- Histórico inexistente
- Colaboração impossível
❌ **Sem servidores provisionados**
- Ambiente de desenvolvimento inexistente
- Homologação não configurada
- Produção não preparada
❌ **Sem processos de desenvolvimento**
- Sem CI/CD
- Sem code review
- Sem gestão de tarefas estruturada
❌ **Sem equipe técnica interna experiente**
- Time sem familiaridade com stacks modernas
- Primeiro contato com React, APIs REST
- Inexperiência com integrações complexas
**Ponto de partida técnico:**
- Protótipo funcional em **WordPress**
- Conteúdo e textos já aprovados
- UX/UI definido
- Regras de negócio documentadas (parcialmente)
---
### Integrações Complexas Requeridas
O MVP precisava integrar com múltiplos sistemas externos:
1. 🔐 **Salesforce** - Registro de pedidos de desconto
2. 📱 **Twilio** - SMS para validação de login (2FA)
3. 🏥 **API oficial de médicos** - Validação de CRM + dados profissionais
4. 🎯 **Interplayers** - Envio de registros de desconto por CPF
5. 📄 **WordPress** - Leitura de conteúdo (CMS headless)
6. 💾 **SQL Server** - Persistência de dados
**Complexidade adicional:**
- Diferentes credenciais/ambientes por integração
- SLAs variados (Twilio crítico, WordPress tolerante)
- Tratamento de erros específico por provider
- Compliance LGPD (dados sensíveis de médicos)
---
## Solution Architecture
### Estratégia: Start Small, Build Solid
**Decisão inicial:** Explicar ao time o processo que iríamos seguir, estabelecendo fundações antes de codificar.
### Fase 1: Setup de Infraestrutura Básica (Semanas 1-2)
Mesmo sem servidores provisionados, iniciei setup essencial:
**Git & Versionamento:**
```bash
# Repositório estruturado desde dia 1
git init
git flow init # Branch strategy definida
# Estrutura de monorepo
/
├── frontend/ # Next.js + React
├── backend/ # .NET APIs
├── cms-adapter/ # WordPress integration
└── docs/ # Arquitetura e ADRs
```
**Processo explicado ao time:**
1. ✅ Tudo no Git (commits atômicos, mensagens descritivas)
2. ✅ Feature branches (nunca commit direto em main)
3. ✅ Code review obrigatório (2 aprovações)
4. ✅ CI/CD preparado (para quando servidores estiverem prontos)
**Ambientes locais primeiro:**
- Docker Compose para desenvolvimento local
- Mock de APIs externas (até credenciais chegarem)
- SQL Server local com seeds de dados
---
### Fase 2: Arquitetura Moderna & Desacoplada
```
┌─────────────────────────────────────────────────────┐
│ FRONTEND (Next.js + React) │
│ - SSR para SEO │
│ - Client-side para interatividade │
│ - Consumo de APIs │
└────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ BACKEND APIs (.NET 7) │
│ - REST APIs │
│ - Authentication/Authorization │
│ - Business logic │
│ - Orchestration layer │
└────┬────┬────┬────┬────┬─────────────────────────┬──┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────────┐ ┌──────────┐
│Salesf. │ │Twilio│ │CRM │ │Interpl.│ │WordPr. │ │SQL Server│
│ │ │ │ │API │ │ │ │(CMS) │ │ │
└────────┘ └──────┘ └──────┘ └────────┘ └────────┘ └──────────┘
```
**Stack escolhido:**
**Frontend:**
- `Next.js 13` - SSR, routing, otimizações
- `React 18` - Componentes, hooks, context
- `TypeScript` - Type safety
- `Tailwind CSS` - Styling moderno
**Backend:**
- `.NET 7` - APIs REST
- `Entity Framework Core` - ORM
- `SQL Server 2019` - Banco de dados
- `Polly` - Resilience patterns (retry, circuit breaker)
**Por que Next.js em vez de manter WordPress?**
- ✅ Performance (SSR vs PHP monolítico)
- ✅ SEO otimizado (critical para farmacêutica)
- ✅ Experiência moderna (SPA quando necessário)
- ✅ Escalabilidade
- ✅ WordPress mantido apenas como CMS (headless)
---
### Fase 3: Integrações (Core do Projeto)
#### 1. Salesforce - Campanhas e Registro de Pedidos
**Solução implementada:**
O Salesforce foi configurado para gerenciar duas funcionalidades principais:
**a) Campanhas de desconto:**
- Marketing configura campanhas no Salesforce (medicamento X, desconto Y%, período)
- Backend consulta campanhas ativas via API
- Frontend (Next.js) exibe percentual de desconto disponível baseado em: medicamento + campanha ativa
**b) Registro de pedidos:**
- Usuário informa: CRM do médico, UF, CPF do paciente, medicamento
- Sistema valida dados (CRM real via API oficial, CPF válido)
- Percentual é calculado automaticamente (campanhas do Salesforce + regras do CMS)
- Pedido é registrado no Salesforce com todos os dados (compliance LGPD)
**Desafios técnicos superados:**
- Autenticação OAuth2 com refresh token automático
- Rate limiting (Salesforce tem limites de API/dia)
- Retry logic para falhas transitórias (Polly)
- Mascaramento de CPF para logs (LGPD)
---
#### 2. Twilio - Autenticação por SMS (2FA)
**Solução implementada:**
Sistema de autenticação de dois fatores para garantir segurança:
**Fluxo de login:**
1. Usuário informa telefone
2. Backend gera código de 6 dígitos (válido por 5 minutos)
3. SMS enviado via Twilio ("Seu código: 123456")
4. Usuário digita código no frontend
5. Backend valida código + timestamp de expiração
6. Token JWT emitido após validação bem-sucedida
**Compliance e auditoria:**
- Telefones mascarados nos logs (LGPD)
- Auditoria completa (quem, quando, qual SMS)
- Taxa de entrega: 99.8%
---
#### 3. API Oficial de Médicos (Conselho Regional de Medicina)
**Solução implementada:**
Validação automática de médicos via API oficial dos conselhos de medicina:
**Validações realizadas:**
- CRM existe e está ativo no conselho
- Nome do médico corresponde ao CRM informado
- Especialidade é permitida (regra de negócio do laboratório)
- UF corresponde ao estado de registro
**Otimizações:**
- Cache de 24 horas para reduzir chamadas à API oficial
- Fallback em caso de API fora do ar (notifica admin)
- Retry automático para falhas transitórias
**Por que isso importa:**
Garante que apenas médicos reais e ativos possam prescrever descontos, evitando fraudes.
---
#### 4. WordPress como CMS Headless
**Solução implementada:**
Marketing continua gerenciando conteúdo no WordPress (familiar), mas frontend é Next.js moderno.
**Arquitetura:**
- WordPress: Gerencia textos, imagens, regras de campanhas
- WordPress REST API: Expõe conteúdo via JSON
- Next.js: Consome API e renderiza com SSR (SEO otimizado)
**Benefícios:**
- ✅ Marketing não precisa aprender nova ferramenta
- ✅ Frontend moderno (performance, UX)
- ✅ SEO otimizado (Server-Side Rendering)
- ✅ Separação clara de responsabilidades (conteúdo vs código)
---
### Fase 4: Resiliência & Error Handling
Com múltiplas integrações externas, falhas são inevitáveis. A solução foi implementar **padrões de resiliência** usando biblioteca Polly (.NET):
**Padrões implementados:**
**1. Retry (Tentar novamente)**
- Se Salesforce/Twilio/CRM API falham, sistema tenta automaticamente 2-3x
- Espera cresce exponencialmente (1s, 2s, 4s) para evitar sobrecarga
- Apenas erros transitórios (timeout, 503) são retentados
**2. Circuit Breaker (Disjuntor)**
- Se serviço falha 5x seguidas, "abre o circuito" por 30s
- Durante 30s, não tenta mais (evita desperdiçar recursos)
- Após 30s, tenta novamente (pode ter voltado)
**3. Timeout**
- Cada integração tem tempo máximo de resposta
- Evita requisições travadas indefinidamente
**4. Fallback (Plano B)**
- Salesforce fora: Pedido vai para fila, processa depois
- Twilio fora: Alerta administrador via email
- CRM API fora: Usa cache (dados de 24h atrás)
- WordPress fora: Exibe conteúdo estático pré-carregado
**Estratégias por integração:**
| Integração | Retry | Circuit Breaker | Timeout | Plano B |
|----------|-------|-----------------|---------|----------|
| Salesforce | 3x (exponencial) | 5 falhas/30s | 10s | Fila de retry |
| Twilio | 2x (linear) | 3 falhas/60s | 5s | Alerta admin |
| CRM API | 3x (exponencial) | Não | 15s | Cache |
| WordPress | Não | Não | 3s | Conteúdo estático |
**Resultado em produção:**
- Salesforce teve manutenção (1h) → Sistema continuou funcionando (fila processou depois)
- Twilio teve instabilidade → Retry automático resolveu 95% dos casos
- Zero downtime percebido pelos usuários
---
## Overcoming Infrastructure Challenges
### Problema: Servidores Não Provisionados
**Solução temporária:**
1. Desenvolvimento 100% local (Docker Compose)
2. Mocks de serviços externos (quando credenciais atrasaram)
3. CI/CD preparado mas não ativo (aguardando infra)
**Quando servidores chegaram (semana 6):**
- Deploy em 2 horas (já estava preparado)
- Zero surpresas (tudo testado localmente)
- Rollout suave
---
### Problema: Credenciais de Integração Atrasadas
**Impacto:** Twilio e Salesforce demoraram 3 semanas para serem provisionadas.
**Solução:** Criar versões "mock" (simuladas) de cada integração:
- Mock do Twilio: Registra no log em vez de enviar SMS real
- Mock do Salesforce: Salva pedido em arquivo JSON local
- Mock da CRM API: Retorna dados fictícios de médicos
**Como funciona:**
- Ambiente de desenvolvimento: Usa mocks (não precisa de credenciais)
- Ambiente de produção: Usa integrações reais (quando credenciais chegarem)
- Troca automática baseada em configuração
**Resultado:** Time continuou 100% produtivo durante 3 semanas, testando fluxos completos sem depender de credenciais.
---
### Problema: Time Inexperiente com Stack Moderno
**Contexto:** Equipe não tinha experiência com React, TypeScript, .NET Core moderno, APIs REST.
**Abordagem de capacitação:**
**1. Pair Programming (1h/dia por desenvolvedor)**
- Tech lead trabalha ao lado do dev
- Compartilhamento de tela + explicação em tempo real
- Dev escreve código, tech lead guia
**2. Code Review Educativo**
- Não apenas "aprovar" ou "reprovar"
- Comentários explicam o **porquê** de cada sugestão
- Exemplo: "Sempre trate erros de requisições! Se API cair, usuário precisa saber o que aconteceu."
**3. Documentação Viva**
- ADRs (Architecture Decision Records): Por que escolhemos X e não Y?
- READMEs: Como rodar, como testar, como deployar
- Onboarding guide: Do zero à primeira feature
**4. Live Coding Semanal (2h)**
- Tech lead resolve problema real ao vivo
- Time observa processo de pensamento
- Q&A ao final
**Resultado:**
- Após 4 semanas, time estava autônomo
- Qualidade de código aumentou consistentemente
- Devs passaram a fazer code review entre si (peer review)
---
## Results & Impact
### Entrega Bem-Sucedida Apesar dos Desafios
**Contexto:** Programa com múltiplas squads trabalhando em paralelo.
**Resultado alcançado:**
- ✅ **MVP entregue em produção com sucesso**
- ✅ Atraso controlado de 2 meses (significativamente menor que outras iniciativas do programa)
- ✅ Todas as integrações funcionando conforme planejado
- ✅ Zero critical bugs em produção (primeira semana)
**Por que a entrega foi bem-sucedida?**
1. **Setup antecipado** - Git, processos, Docker local desde dia 1
2. **Mocks estratégicos** - Time não ficou bloqueado esperando infra
3. **Arquitetura sólida** - Resiliência desde o início
4. **Upskilling contínuo** - Time aprendeu fazendo
5. **Comunicação proativa** - Riscos reportados cedo
---
### Métricas do MVP
**Performance:**
- ⚡ Tempo de carregamento: <2s (95th percentile)
- 📱 Lighthouse score: 95+ (mobile)
- 🔒 SSL A+ rating
**Integrações:**
- 📊 Salesforce: 100% de pedidos sincronizados
- 📱 Twilio: 99.8% delivery rate
- 🏥 CRM API: 10k validações/dia (média)
- 💾 SQL Server: 50k registros/mês
**Adoção:**
- 👨‍⚕️ 2.000+ médicos cadastrados (3 primeiros meses)
- 🎯 15.000+ pedidos de desconto processados
- ⭐ 4.8/5 satisfação (pesquisa interna)
---
### Impacto no Cliente
**Transformação digital iniciada:**
- ✅ Git implementado e adotado
- ✅ Processos de desenvolvimento estabelecidos
- ✅ Time interno capacitado em stacks modernas
- ✅ Infraestrutura cloud configurada (Azure)
- ✅ Roadmap de evolução definido
**Base para futuros projetos:**
- Arquitetura serviu de referência para outras iniciativas
- Padrões de código documentados (coding standards)
- Pipelines CI/CD reutilizados
---
## Tech Stack
`.NET 7` `C#` `Entity Framework Core` `SQL Server` `React 18` `Next.js 13` `TypeScript` `Tailwind CSS` `Salesforce API` `Twilio` `WordPress REST API` `Docker` `Polly` `OAuth2` `JWT` `LGPD Compliance`
---
## Key Decisions & Trade-offs
### Por que Next.js em vez de React puro?
**Requisitos:**
- SEO crítico (farmacêutica precisa rankar)
- Performance (médicos usam mobile)
- Conteúdo dinâmico (WordPress)
**Next.js oferece:**
- ✅ SSR out-of-the-box
- ✅ API routes (BFF pattern)
- ✅ Otimizações automáticas (image, fonts)
- ✅ Deploy simplificado (Vercel, Azure)
---
### Por que manter WordPress?
**Alternativas consideradas:**
1. ❌ Migrar conteúdo para banco + CMS custom (tempo)
2. ❌ Strapi/Contentful (custos + learning curve)
3. ✅ **WordPress headless** (melhor trade-off)
**Vantagens:**
- Time de marketing já sabe usar
- Conteúdo aprovado já estava lá
- WordPress REST API é sólida
- Custo zero (já estava rodando)
---
### Por que .NET 7 em vez de Node.js?
**Contexto:** Cliente tinha preferência por Microsoft stack.
**Benefícios adicionais:**
- Performance superior (vs Node em APIs)
- Type safety nativa (C#)
- Entity Framework (ORM maduro)
- Integração fácil com Azure (deploy futuro)
- Time do cliente tinha familiaridade
---
## Lessons Learned
### 1. Infraestrutura Atrasada? Prepare Alternativas
Não espere servidores/credenciais para começar:
- Docker local é seu amigo
- Mocks permitem progresso
- CI/CD pode ser preparado antes de haver onde deployar
**Lição:** Controle o que você pode controlar.
---
### 2. Processos > Ferramentas
Mesmo sem Git corporativo, estabeleci:
- Branching strategy
- Code review
- Commit conventions
- Documentation standards
**Resultado:** Quando ferramentas chegaram, time já sabia usá-las.
---
### 3. Upskilling é Investimento, Não Custo
Pair programming e code reviews levaram tempo, mas:
- ✅ Time ficou autônomo mais rápido
- ✅ Qualidade de código aumentou
- ✅ Knowledge sharing natural
- ✅ Onboarding de novos devs simplificado
---
### 4. Resiliência Desde o Início
Implementar Polly (retry, circuit breaker) no início salvou em produção:
- Twilio teve instabilidade (resolvida automaticamente)
- Salesforce teve manutenção (queue funcionou)
- CRM API teve lentidão (cache mitigou)
**Lição:** Não deixe resiliência para "depois". Falhas vão acontecer.
---
### 5. Comunicação Clara de Riscos
Reportei semanalmente:
- Bloqueios (infraestrutura, credenciais)
- Riscos (prazos, dependências)
- Soluções alternativas (mocks, workarounds)
**Resultado:** Stakeholders sabiam exatamente o status e não tiveram surpresas.
---
## Challenges & How They Were Overcome
| Desafio | Impacto | Solução | Resultado |
|---------|---------|---------|-----------|
| Sem Git | Bloqueio total | Setup local + GitLab Cloud | Time produtivo dia 1 |
| Sem servidores | Sem ambiente de dev | Docker Compose local | Dev/test local completo |
| Credenciais atrasadas | Integração bloqueada | Mock services | Progresso sem bloqueio |
| Time inexperiente | Código de baixa qualidade | Pair prog + Code review | Ramp-up em 4 semanas |
| Múltiplas integrações | Complexidade alta | Polly + patterns | Zero downtime prod |
---
## Next Steps (Pós-MVP)
**Roadmap sugerido ao cliente:**
1. **Fase 2: Expansão de funcionalidades**
- Dashboard para médicos (histórico de pedidos)
- Notificações push (Firebase)
- Integração com e-commerce (compra direta)
2. **Fase 3: Otimizações**
- Cache distribuído (Redis)
- CDN para assets estáticos
- Analytics avançado (Amplitude)
3. **Fase 4: Escala**
- Kubernetes (AKS)
- Microserviços (quebrar monolito)
- Event-driven architecture (Azure Service Bus)
---
**Resultado:** MVP entregue em produção apesar de começar literalmente do zero, estabelecendo fundações sólidas para transformação digital do cliente.
[Precisa construir um MVP em cenário desafiador? Entre em contato](#contact)

View File

@ -0,0 +1,211 @@
---
title: "Sistema de Integração SAP Healthcare"
slug: "sap-integration-healthcare"
summary: "Integração bidirecional processando 100k+ transações/dia com 99.9% uptime"
client: "Confidencial - Multinacional Healthcare"
industry: "Healthcare"
timeline: "6 meses"
role: "Arquiteto de Integração"
image: ""
tags:
- SAP
- C#
- .NET
- Integrações
- Enterprise
- Healthcare
featured: true
order: 1
date: 2023-06-15
seo_title: "Case: Integração SAP Healthcare - 100k Transações/Dia"
seo_description: "Como arquitetamos sistema de integração SAP processando 100k+ transações diárias com 99.9% uptime para empresa healthcare."
seo_keywords: "integração SAP, C#, .NET, SAP Connector, enterprise integration, healthcare"
---
## Overview
**Cliente:** Multinacional Healthcare (confidencial)
**Porte:** 100.000+ funcionários
**Projeto:** Integração de benefícios
**Timeline:** 6 meses
**Meu Role:** Arquiteto de Integração
---
## Desafio
O cliente tinha sistema interno de gestão de benefícios que precisava sincronizar com SAP ECC para processar folha de pagamento.
### Dores principais:
- Processo manual sujeito a erros
- 3-5 dias de delay entre sistemas
- 100k funcionários esperando processamento
- Picos de carga (final de mês)
### Constraints:
- Budget limitado (sem SAP BTP)
- Time SAP interno pequeno (2 desenvolvedores)
- Prazo apertado (6 meses go-live)
- Sistema legado .NET Framework 4.5
---
## Solução
Arquitetura de integração bidirecional:
```
[Sistema Interno] ←→ [Queue] ←→ [SAP Connector] ←→ [SAP ECC]
↓ ↓
[MongoDB Logs] [ABAP Z_BENEFITS]
```
### Componentes:
- .NET Service com SAP Connector (NCo 3.0)
- ABAP transaction customizada (Z_BENEFITS)
- Queue system (RabbitMQ) para retry logic
- MongoDB para auditoria e troubleshooting
- Scheduler (Hangfire) para batch processing
### Fluxo:
1. Sistema gera mudanças (new hire, alterações)
2. Service processa batch (500 registros/vez)
3. SAP Connector chama Z_BENEFITS via RFC
4. SAP retorna status (sucesso/erro)
5. Retry automático se falha (max 3x)
6. Logs MongoDB para troubleshooting
---
## Resultado
### Métricas:
- **100k+** transações/dia processadas
- **99.9%** uptime
- Redução de **5 dias → 4 horas** (delay)
- **80%** redução tempo processamento
- **Zero** erros manuais (vs 2-3% antes)
### Benefícios:
- Funcionários recebem benefícios on-time
- Time RH economiza 40h/mês (trabalho manual)
- Auditoria completa (compliance)
- Escalável (crescimento 30% ano sem refactor)
---
## Tech Stack
`C#` `.NET Framework 4.5` `SAP NCo 3.0` `RabbitMQ` `MongoDB` `Hangfire` `Docker` `SAP ECC` `ABAP` `RFC`
---
## Decisões & Motivação
### 💡 Decisão 1: SAP Connector vs SAP BTP
**Opções avaliadas:**
- SAP BTP (eventos, APIs modernas, cloud)
- SAP Connector (RFC direto, on-premise)
**Escolhemos:** SAP Connector
**Motivação:**
- Cliente tinha SAP ECC on-premise (não S/4)
- Budget não permitia licença BTP
- Time SAP confortável com ABAP/RFC
- Necessidades atendidas com RFC (não precisava event-driven real-time)
**Trade-off aceito:**
- Menos "moderno" que BTP, mas 100% funcional
- Custo R$ 0 adicional vs R$ 150k+/ano BTP
- Delivery 2 meses mais rápido (sem learning curve BTP)
---
### 💡 Decisão 2: Queue System vs Calls Diretos
**Opções avaliadas:**
- Chamadas síncronas diretas (mais simples)
- Queue com retry (mais complexo)
**Escolhemos:** Queue + Retry
**Motivação:**
- SAP ocasionalmente indisponível (manutenção)
- Picos de carga (final do mês = 200k reqs)
- Garantir zero perda de dados
- Resiliência > simplicidade (ambiente crítico)
**Implementação:**
- RabbitMQ com dead-letter queue
- Retry exponencial (1min, 5min, 15min)
- Alertas se 3 falhas consecutivas
**Resultado:**
- Zero perda dados em 2 anos produção
- Time RH não precisa "ficar de olho"
---
### 💡 Decisão 3: ABAP Customizado vs Standard
**Opções avaliadas:**
- BAPIs standard SAP (zero código ABAP)
- Transaction customizada (Z_BENEFITS)
**Escolhemos:** Transaction customizada
**Motivação:**
- BAPIs standard não tinham validações específicas do negócio
- Cliente queria lógica centralizada no SAP (single source of truth)
- Permitiu validações complexas (elegibilidade, dependentes, limites)
**Trade-off:**
- Requer manutenção ABAP (time SAP interno)
- Mas: Cliente preferiu vs lógica dupla (risco dessincronia)
---
### ❌ Alternativas NÃO Escolhidas
**Webhook/Callback (Event-Driven):**
- Cliente não tinha infraestrutura expor APIs
- Sistema interno atrás de firewall
- Polling batch funciona bem para o caso
**Microserviços Kubernetes:**
- Overkill para integração única
- Time não tinha expertise K8s
- Docker simples suficiente
**Real-time Sync (<1min):**
- Negócio não precisa (batch diário ok)
- Custo infra aumentaria 3x
- 4h delay é aceitável para folha
---
## Aprendizados
### ✅ O que funcionou muito bem:
- Envolver time SAP desde dia 1 (buy-in)
- MongoDB para logs (troubleshooting 10x mais rápido)
- Retry logic salvou inúmeras vezes
### 🔄 O que faria diferente:
- Adicionar health check endpoint mais cedo
- Dashboard de monitoramento desde início (adicionamos depois)
### 📚 Lições para próximos projetos:
- Cliente "budget limitado" ≠ "solução limitada" - criatividade resolve
- Documentar TODAS decisões arquiteturais (team turnover)
- Simplicidade vence complexidade quando ambas funcionam (KISS)
---
## Precisa de Algo Similar?
Integrações SAP complexas, sistemas legados, ou arquitetura de alta disponibilidade?
[Vamos conversar sobre seu desafio →](/#contact)

View File

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Mvc;
using CarneiroTech.Services;
namespace CarneiroTech.Controllers;
public class CasesController : Controller
{
private readonly ICaseService _caseService;
private readonly ILogger<CasesController> _logger;
public CasesController(ICaseService caseService, ILogger<CasesController> logger)
{
_caseService = caseService;
_logger = logger;
}
// GET: /cases
public async Task<IActionResult> Index(string? tag)
{
var cases = string.IsNullOrEmpty(tag)
? await _caseService.GetAllCasesAsync()
: await _caseService.GetCasesByTagAsync(tag);
var allTags = await _caseService.GetAllTagsAsync();
ViewData["Title"] = string.IsNullOrEmpty(tag)
? "Cases - Carneiro Tech"
: $"Cases: {tag} - Carneiro Tech";
ViewData["Description"] = string.IsNullOrEmpty(tag)
? "Explore our portfolio of technical consulting projects and solution design cases."
: $"Technical consulting cases related to {tag}.";
ViewData["SelectedTag"] = tag;
ViewBag.AllTags = allTags;
return View(cases);
}
// GET: /cases/{slug}
[Route("cases/{slug}")]
public async Task<IActionResult> Details(string slug)
{
var caseModel = await _caseService.GetCaseBySlugAsync(slug);
if (caseModel == null)
{
_logger.LogWarning($"Case not found: {slug}");
return NotFound();
}
ViewData["Title"] = !string.IsNullOrEmpty(caseModel.Metadata.SeoTitle)
? caseModel.Metadata.SeoTitle
: caseModel.Metadata.Title;
ViewData["Description"] = !string.IsNullOrEmpty(caseModel.Metadata.SeoDescription)
? caseModel.Metadata.SeoDescription
: caseModel.Metadata.Summary;
ViewData["Keywords"] = caseModel.Metadata.SeoKeywords;
ViewData["OgImage"] = caseModel.Metadata.Image;
return View(caseModel);
}
}

View File

@ -0,0 +1,100 @@
using System.Diagnostics;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using CarneiroTech.Models;
using CarneiroTech.Services;
namespace CarneiroTech.Controllers;
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ICaseService _caseService;
public HomeController(ILogger<HomeController> logger, ICaseService caseService)
{
_logger = logger;
_caseService = caseService;
}
public async Task<IActionResult> Index()
{
var featuredCases = await _caseService.GetFeaturedCasesAsync();
ViewData["Title"] = "Carneiro Tech - Solution Design & Technical Consulting";
ViewData["Description"] = "20+ years connecting business and technology. Specialized in technical proposals, MVP definition, and due diligence.";
ViewData["Keywords"] = "solution design, technical consulting, SAP integration, enterprise architecture, MVP definition, due diligence";
return View(featuredCases);
}
public IActionResult Privacy()
{
ViewData["Title"] = "Privacy Policy - Carneiro Tech";
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
[Route("sitemap.xml")]
public async Task<IActionResult> Sitemap()
{
var cases = await _caseService.GetAllCasesAsync();
var sitemap = new StringBuilder();
sitemap.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sitemap.AppendLine("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
// Homepage
sitemap.AppendLine($@" <url>
<loc>https://carneirotech.com/</loc>
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>");
// Cases index
sitemap.AppendLine($@" <url>
<loc>https://carneirotech.com/cases</loc>
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>");
// Each case
foreach (var c in cases)
{
var lastMod = c.Metadata.Date != DateTime.MinValue ? c.Metadata.Date : DateTime.UtcNow;
sitemap.AppendLine($@" <url>
<loc>https://carneirotech.com/cases/{c.Metadata.Slug}</loc>
<lastmod>{lastMod:yyyy-MM-dd}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>");
}
sitemap.AppendLine("</urlset>");
return Content(sitemap.ToString(), "application/xml");
}
[HttpPost]
public IActionResult Contact(ContactFormModel model)
{
if (!ModelState.IsValid)
{
TempData["Error"] = "Por favor, preencha todos os campos corretamente.";
return RedirectToAction("Index", new { fragment = "contact" });
}
// TODO: Implement email sending logic here (SendGrid, SMTP, etc.)
_logger.LogInformation($"Contact form submitted: {model.Name} - {model.Email}");
TempData["Success"] = "Mensagem enviada com sucesso! Retornaremos em breve.";
return RedirectToAction("Index", new { fragment = "contact" });
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Mvc;
using CarneiroTech.Services;
namespace CarneiroTech.Controllers
{
public class LanguageController : Controller
{
private readonly ILanguageService _languageService;
public LanguageController(ILanguageService languageService)
{
_languageService = languageService;
}
[HttpPost]
public IActionResult SetLanguage(string language, string returnUrl = "/")
{
_languageService.SetLanguage(HttpContext, language);
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
}
}

363
DEPLOY_README.md Normal file
View File

@ -0,0 +1,363 @@
# CarneiroTech - Deployment Guide
## 🚀 Deploy para Produção (OCI)
O site CarneiroTech está hospedado no servidor OCI (Oracle Cloud Infrastructure) com IP final **218**.
### Arquitetura
```
Internet
Nginx (Port 80/443)
Docker Container (Port 5008)
ASP.NET Core MVC App
```
---
## 📋 Informações do Servidor
- **Servidor**: `ubuntu@129.146.116.218`
- **Diretório**: `/home/ubuntu/apps/carneirotech`
- **Container**: `carneirotech-web`
- **Porta Interna**: `5008`
- **Domínio**: `carneirotech.com`
- **SSL**: Let's Encrypt (auto-renovação configurada)
---
## 🔧 Como Fazer Deploy
### Opção 1: Deploy Automático (Recomendado)
Execute o script de deploy da sua máquina local:
```bash
cd /mnt/c/Users/ricar/Nextcloud/CarneiroTech/Site/aspnet/CarneiroTech
./deploy-to-oci.sh
```
O script irá:
1. ✅ Criar pacote compactado excluindo bin/obj
2. ✅ Transferir para servidor via SCP
3. ✅ Extrair arquivos
4. ✅ Fazer backup da imagem anterior
5. ✅ Fazer build da nova imagem
6. ✅ Parar container antigo
7. ✅ Subir container novo
8. ✅ Verificar health check
9. ✅ Limpar imagens antigas (mantém últimas 3)
**Rollback automático:** Se o health check falhar, o script restaura automaticamente a versão anterior.
---
### Opção 2: Deploy Manual
1. Conectar no servidor:
```bash
ssh ubuntu@129.146.116.218
cd ~/apps/carneirotech
```
2. Transferir arquivos alterados (da sua máquina):
```bash
scp arquivo.cs ubuntu@129.146.116.218:~/apps/carneirotech/Controllers/
```
3. No servidor, fazer rebuild e restart:
```bash
./deploy.sh
```
---
## 🔍 Comandos Úteis
### Ver logs do container
```bash
ssh ubuntu@129.146.116.218 "docker logs carneirotech-web --tail=50 -f"
```
### Verificar status
```bash
ssh ubuntu@129.146.116.218 "docker ps --filter name=carneirotech"
```
### Entrar no container
```bash
ssh ubuntu@129.146.116.218 "docker exec -it carneirotech-web bash"
```
### Reiniciar container
```bash
ssh ubuntu@129.146.116.218 "cd ~/apps/carneirotech && docker compose restart"
```
### Ver logs do Nginx
```bash
ssh ubuntu@129.146.116.218 "sudo tail -f /var/log/nginx/access.log"
ssh ubuntu@129.146.116.218 "sudo tail -f /var/log/nginx/error.log"
```
### Testar Nginx config
```bash
ssh ubuntu@129.146.116.218 "sudo nginx -t"
```
### Recarregar Nginx
```bash
ssh ubuntu@129.146.116.218 "sudo systemctl reload nginx"
```
---
## 📁 Estrutura de Arquivos no Servidor
```
/home/ubuntu/apps/carneirotech/
├── Content/ # Markdown files dos cases
├── Controllers/ # MVC Controllers
├── Models/ # Models
├── Services/ # Services (CaseService)
├── Views/ # Razor Views
├── wwwroot/ # Static files (CSS, JS, images)
├── Dockerfile # Docker build instructions (ARM64)
├── docker-compose.yml # Docker Compose config
├── deploy.sh # Deploy script (no servidor)
└── CarneiroTech.csproj # Project file
/etc/nginx/sites-available/
└── carneirotech.com.conf # Nginx proxy config
/etc/letsencrypt/live/carneirotech.com/
├── fullchain.pem # SSL certificate
└── privkey.pem # SSL private key
```
---
## 🔐 SSL / HTTPS
O certificado SSL é gerenciado automaticamente pelo **Let's Encrypt** via Certbot.
### Renovação Automática
O Certbot está configurado para renovar automaticamente. Verifique o status:
```bash
ssh ubuntu@129.146.116.218 "sudo certbot certificates"
```
### Renovar Manualmente (se necessário)
```bash
ssh ubuntu@129.146.116.218 "sudo certbot renew"
```
### Adicionar www.carneirotech.com
Quando você adicionar o registro DNS para `www.carneirotech.com`:
1. Adicionar ao Namecheap (A record apontando para 129.146.116.218)
2. Atualizar certificado SSL:
```bash
ssh ubuntu@129.146.116.218 "sudo certbot certonly --nginx -d carneirotech.com -d www.carneirotech.com --expand"
```
3. Atualizar nginx config:
```bash
ssh ubuntu@129.146.116.218 "sudo nano /etc/nginx/sites-available/carneirotech.com.conf"
```
Adicionar `www.carneirotech.com` no `server_name` de ambos os blocos server.
4. Recarregar nginx:
```bash
ssh ubuntu@129.146.116.218 "sudo nginx -t && sudo systemctl reload nginx"
```
---
## 🧹 Manutenção
### Limpar imagens Docker antigas
```bash
ssh ubuntu@129.146.116.218 "docker image prune -a"
```
### Limpar containers parados
```bash
ssh ubuntu@129.146.116.218 "docker container prune"
```
### Ver uso de disco
```bash
ssh ubuntu@129.146.116.218 "df -h"
```
### Ver uso de Docker
```bash
ssh ubuntu@129.146.218 "docker system df"
```
---
## 🐛 Troubleshooting
### Site não carrega (502 Bad Gateway)
1. Verificar se container está rodando:
```bash
ssh ubuntu@129.146.116.218 "docker ps --filter name=carneirotech"
```
2. Ver logs do container:
```bash
ssh ubuntu@129.146.116.218 "docker logs carneirotech-web --tail=100"
```
3. Reiniciar container:
```bash
ssh ubuntu@129.146.116.218 "cd ~/apps/carneirotech && docker compose restart"
```
### Container não sobe (unhealthy)
1. Ver logs detalhados:
```bash
ssh ubuntu@129.146.116.218 "docker logs carneirotech-web"
```
2. Verificar health check manualmente:
```bash
ssh ubuntu@129.146.116.218 "curl http://localhost:5008/"
```
3. Entrar no container para debug:
```bash
ssh ubuntu@129.146.116.218 "docker exec -it carneirotech-web bash"
```
### Build falha
1. Verificar se há espaço em disco:
```bash
ssh ubuntu@129.146.116.218 "df -h"
```
2. Limpar cache do Docker:
```bash
ssh ubuntu@129.146.116.218 "docker builder prune -a"
```
3. Fazer build com logs detalhados:
```bash
ssh ubuntu@129.146.116.218 "cd ~/apps/carneirotech && docker compose build --no-cache --progress=plain"
```
### SSL expirado
Renovar manualmente:
```bash
ssh ubuntu@129.146.116.218 "sudo certbot renew --force-renewal"
```
---
## 📊 Monitoramento
### Health Check Endpoint
O container tem health check automático configurado:
- **URL**: `http://localhost:5008/`
- **Intervalo**: 30s
- **Timeout**: 3s
- **Start Period**: 40s
### Ver status de saúde
```bash
ssh ubuntu@129.146.116.218 "docker inspect carneirotech-web | grep -A 10 Health"
```
---
## 🔄 Workflow de Desenvolvimento
1. **Desenvolver localmente** (Windows)
```bash
cd C:\Users\ricar\Nextcloud\CarneiroTech\Site\aspnet\CarneiroTech
dotnet run
```
2. **Testar mudanças** (http://localhost:5000)
3. **Fazer deploy para produção**
```bash
./deploy-to-oci.sh
```
4. **Verificar em produção** (https://carneirotech.com)
---
## 📝 Notas Importantes
- ⚠️ O servidor usa **ARM64** (Ampere). O Dockerfile está otimizado para isso.
- ⚠️ Não use `docker-compose` sem `deploy.sh` - pode causar downtime sem rollback.
- ⚠️ Sempre teste localmente antes de fazer deploy.
- ⚠️ O script de deploy faz backup automático - use sem medo!
- ⚠️ Arquivos Markdown (Content/Cases/*.md) são incluídos no build.
---
## ✅ Checklist de Deploy
Antes de fazer deploy, verifique:
- [ ] Código compilou localmente sem erros
- [ ] Testou localmente (dotnet run)
- [ ] Commitou mudanças no Git (quando configurar)
- [ ] Executou `./deploy-to-oci.sh`
- [ ] Verificou logs: `docker logs carneirotech-web`
- [ ] Testou site: `https://carneirotech.com`
- [ ] Verificou que cases Markdown aparecem corretamente
---
## 🆘 Suporte
Em caso de problemas críticos:
1. Ver logs em tempo real:
```bash
ssh ubuntu@129.146.116.218 "docker logs carneirotech-web -f"
```
2. Rollback para versão anterior (automático no deploy.sh)
3. Se precisar voltar manualmente:
```bash
ssh ubuntu@129.146.116.218
cd ~/backups/carneirotech
# Ver backups disponíveis
docker images | grep carneirotech-web
# Usar backup específico
docker tag <BACKUP_IMAGE_ID> carneirotech-carneirotech-web
cd ~/apps/carneirotech
docker compose up -d
```
---
**Deployment criado em:** 21 de Dezembro de 2024
**Última atualização:** 21 de Dezembro de 2024
**Servidor:** OCI (Oracle Cloud) - ARM64 (Ampere)
**Status:** ✅ Produção

44
Dockerfile Normal file
View File

@ -0,0 +1,44 @@
# Dockerfile for ASP.NET Core MVC on ARM64 (Ampere/OCI)
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy-arm64v8 AS build
WORKDIR /src
# Copy csproj and restore dependencies
COPY ["CarneiroTech.csproj", "./"]
RUN dotnet restore "CarneiroTech.csproj" -a arm64
# Copy everything else and build
COPY . .
RUN dotnet build "CarneiroTech.csproj" -c Release -o /app/build -a arm64
# Publish stage
FROM build AS publish
RUN dotnet publish "CarneiroTech.csproj" -c Release -o /app/publish -a arm64 /p:UseAppHost=false
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-arm64v8 AS final
WORKDIR /app
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Copy published app
COPY --from=publish /app/publish .
# Copy Content folder (markdown files) - already included in publish
# COPY Content ./Content
# Expose port
EXPOSE 5008
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:5008/ || exit 1
# Set environment variables
ENV ASPNETCORE_URLS=http://+:5008
ENV ASPNETCORE_ENVIRONMENT=Production
# Run app
ENTRYPOINT ["dotnet", "CarneiroTech.dll"]

308
IMPLEMENTATION_SUMMARY.md Normal file
View File

@ -0,0 +1,308 @@
# Carneiro Tech - Implementation Summary
## What Has Been Built
A complete, production-ready ASP.NET MVC Core website for Carneiro Tech with the following features:
### ✅ Core Features Implemented
1. **Modern, Responsive Design**
- Agency Bootstrap template fully integrated
- Custom CSS with logo colors (#ffc800 yellow/gold)
- Mobile-first responsive layout
- Professional consulting appearance
2. **Markdown-Based Portfolio System**
- Easy case management with .md files
- YAML front matter for metadata
- Automatic HTML conversion
- In-memory caching (60 min)
- Tag-based filtering
3. **SEO Optimized**
- Dynamic meta tags (title, description, keywords)
- Open Graph tags for social sharing
- Twitter Card support
- JSON-LD structured data (Organization schema)
- XML sitemap at `/sitemap.xml`
- robots.txt configured
4. **Portfolio/Cases Section**
- Homepage with featured cases (up to 6)
- Full cases listing page at `/cases`
- Individual case detail pages at `/cases/{slug}`
- Tag filtering functionality
- Custom hover effects with overlay text
- Gradient backgrounds for cases without images
5. **Docker Ready**
- Multi-stage Dockerfile
- docker-compose.yml for easy deployment
- .dockerignore for optimized builds
- Volume mapping for Content folder
## Project Structure
```
aspnet/CarneiroTech/
├── Controllers/
│ ├── HomeController.cs # Homepage, sitemap, contact
│ └── CasesController.cs # Cases list & details
├── Models/
│ ├── CaseModel.cs # Case data model
│ ├── CaseMetadata.cs # YAML front matter model
│ ├── ContactFormModel.cs # Contact form validation
│ └── SitemapItem.cs # Sitemap data model
├── Services/
│ ├── IMarkdownService.cs # Markdown parsing interface
│ ├── MarkdownService.cs # Markdown implementation
│ ├── ICaseService.cs # Case management interface
│ └── CaseService.cs # Case management implementation
├── Views/
│ ├── Home/
│ │ └── Index.cshtml # Homepage with all sections
│ ├── Cases/
│ │ ├── Index.cshtml # Cases listing with filters
│ │ └── Details.cshtml # Individual case page
│ └── Shared/
│ └── _Layout.cshtml # Main layout with SEO
├── Content/
│ └── Cases/
│ ├── sap-integration-healthcare.md # Case 1
│ ├── legacy-modernization.md # Case 2
│ └── mvp-definition.md # Case 3
├── wwwroot/
│ ├── css/
│ │ ├── styles.css # Bootstrap Agency theme
│ │ └── custom.css # Custom Carneiro Tech styles
│ ├── js/
│ │ └── scripts.js # Bootstrap theme scripts
│ ├── img/
│ │ └── logo.svg # Carneiro Tech logo
│ └── robots.txt
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── README.md
└── IMPLEMENTATION_SUMMARY.md (this file)
```
## Homepage Sections
The homepage includes all major sections:
1. **Hero/Masthead**
- "Conectando Negócio e Tecnologia" tagline
- Call-to-action button to services
2. **Services Section** (#services)
- Solution Design
- Technical Consulting
- Technical Proposals
3. **Portfolio/Cases** (#portfolio)
- Featured cases (up to 6)
- Link to full cases page
4. **About Section** (#about)
- Timeline with 4 milestones
- Professional journey
- Icons for each phase
5. **Contact Form** (#contact)
- Name, Email, Phone, Message fields
- Form validation
- Success/Error messages
- Note: Email sending needs to be implemented (TODO comment in code)
## Three Example Cases Created
### 1. SAP Integration Healthcare
- **Slug:** `sap-integration-healthcare`
- **Topic:** Enterprise integration with SAP ECC
- **Tags:** SAP, C#, .NET, Integrações, Enterprise, Healthcare
- **Highlights:** 100k+ transactions/day, 99.9% uptime
- **Featured:** Yes
### 2. Legacy Modernization
- **Slug:** `legacy-modernization`
- **Topic:** Migration from monolith to microservices
- **Tags:** .NET, Azure, Microserviços, Cloud, Modernização, Arquitetura
- **Highlights:** Strangler pattern, AKS, zero downtime
- **Featured:** Yes
### 3. MVP Definition
- **Slug:** `mvp-definition`
- **Topic:** Startup consulting for MVP validation
- **Tags:** MVP, Product Design, Technical Consulting, Startup, EdTech, Strategy
- **Highlights:** Reduced scope 50→8 features, validated with 1000+ users
- **Featured:** Yes
Each case includes:
- Overview section
- Challenge description
- Solution architecture
- Results/metrics
- Tech stack
- Decision-making process (why certain choices were made)
- Lessons learned
## Key Features & Customizations
### Portfolio Hover Effect
- Yellow overlay (#ffc800) on hover
- Text summary appears on hover
- Smooth transitions
- Works on all devices
### Color Scheme
- Primary: #ffc800 (yellow/gold from logo)
- Dark: #212529
- Gradients for backgrounds without images
- Professional, modern feel
### SEO Features
All pages include:
- Title (max 60 chars recommended)
- Description (max 160 chars)
- Keywords
- Canonical URLs
- Open Graph tags
- Twitter Cards
- Structured data (JSON-LD)
### Performance Optimizations
- In-memory caching (cases cached for 60 min)
- Static file serving
- Minified CSS/JS from CDN
- Lazy loading compatible
## How to Run
### Local Development
```bash
cd aspnet/CarneiroTech
dotnet run
```
Open: `http://localhost:5000`
### Docker
```bash
cd aspnet/CarneiroTech
docker-compose up -d
```
Open: `http://localhost:8080`
## Next Steps / TODOs
### Essential for Production:
1. **Replace placeholder logo colors**
- Extract actual colors from `/logo/LogoNovo.svg`
- Update custom.css color variables
2. **Update LinkedIn URL**
- In `_Layout.cshtml` footer
- Currently: `https://linkedin.com/in/ricardo-carneiro`
3. **Implement Contact Form Email**
- Add SendGrid or SMTP configuration
- Update `HomeController.Contact` method
- Add email credentials to appsettings.json
4. **Add Real Portfolio Images**
- Currently cases use gradient backgrounds
- Add images to `/wwwroot/img/cases/`
- Update markdown front matter `image:` field
5. **Update Domain URLs**
- Replace `carneirotech.com` with your actual domain
- Files to update:
- `_Layout.cshtml` (canonical, OG tags)
- `HomeController.cs` (sitemap)
### Nice to Have:
1. **Analytics**
- Add Google Analytics 4
- Add tracking code to `_Layout.cshtml`
2. **Dark Mode**
- Implement toggle switch
- CSS variables for theming
3. **RSS Feed**
- Create `/feed.xml` endpoint
- List all cases
4. **Search Functionality**
- Add search bar
- Filter cases by text
5. **Admin Panel**
- CRUD for cases (instead of editing .md files)
- Cache invalidation button
6. **Performance**
- Image optimization (WebP format)
- Lazy loading for images
- CDN for static assets
## Testing Checklist
- [x] Build succeeds without errors
- [x] All routes defined
- [x] Markdown parsing works
- [x] SEO meta tags present
- [x] Sitemap generates
- [ ] Run locally and verify homepage
- [ ] Verify cases listing page
- [ ] Verify individual case pages
- [ ] Test tag filtering
- [ ] Test contact form validation
- [ ] Test responsive design (mobile/tablet)
- [ ] Test in different browsers
- [ ] Validate HTML
- [ ] Test Docker build
- [ ] Test Docker run
## Deployment Checklist
- [ ] Update all placeholder URLs
- [ ] Add real logo and images
- [ ] Configure email sending
- [ ] Set up SSL certificate
- [ ] Configure production environment variables
- [ ] Test in staging environment
- [ ] Set up monitoring/logging
- [ ] Configure backup for Content folder
- [ ] Set up CI/CD pipeline (optional)
- [ ] Update DNS records
- [ ] Test production deployment
## Support & Documentation
- **README.md** - Complete setup and usage guide
- **This file** - Implementation overview
- **Code comments** - Inline documentation
- **Markdown examples** - 3 complete case studies
## Technologies Used
- **ASP.NET Core 8** - Web framework
- **C# 12** - Programming language
- **Markdig 0.44.0** - Markdown parsing
- **YamlDotNet 16.3.0** - YAML parsing
- **Bootstrap 5** - CSS framework
- **Font Awesome 6** - Icons
- **Docker** - Containerization
---
**Built on:** 2025-12-19
**Framework:** ASP.NET MVC Core 8
**Status:** Production Ready (pending customizations listed above)

215
MUDANCAS_TEMA_VERMELHO.md Normal file
View File

@ -0,0 +1,215 @@
# Mudanças Implementadas - Tema Vermelho
## ✅ Alterações Realizadas
### 1. **Esquema de Cores - Do Amarelo para Vermelho**
Todas as cores amarelas foram substituídas por tons de vermelho inspirados no logo:
**Paleta de Cores:**
- **Vermelho Principal:** `#C42127` (vermelho vibrante do logo)
- **Vermelho Escuro:** `#8B1E23` (tom mais escuro para hover/sombras)
- **Vermelho Accent:** `#E63946` (vermelho brilhante para destaques)
- **Fundo Claro:** `#FAF7F5` (bege/creme quente)
- **Creme:** `#FFF8F0` (creme claro para navbar)
### 2. **Menu/Navbar com Fundo Claro**
**Antes:** Navbar escuro (dark)
**Depois:** Navbar com fundo bege claro (`#FAF7F5`)
**Mudanças:**
- Fundo claro e confortável
- Links do menu em cinza escuro (`#2c3e50`)
- Hover em vermelho (`#C42127`)
- Sombra sutil para destaque
- Menu mobile com fundo creme
- Ícone do menu (hamburguer) em vermelho
### 3. **Logo Otimizado**
**Antes:** `logo.svg` (745KB, muito pesado)
**Depois:** `logo-optimized.svg` (<2KB, otimizado)
**Características do novo logo:**
- **Fundo transparente**
- Design de espiral/concha inspirado no logo original
- Gradiente vermelho
- Tamanho reduzido 370x
- Altura aumentada de 40px para 45px (melhor visibilidade)
**Arquivo:** `/wwwroot/img/logo-optimized.svg`
### 4. **Efeito Hover no Portfolio - Corrigido**
**Problema:** Quadrado amarelo pequeno e sem sentido no hover
**Solução:**
- Overlay vermelho cobrindo **toda a imagem** do portfolio
- Texto do resumo aparece em branco sobre fundo vermelho
- Transição suave de opacidade
- Card levanta levemente no hover (translateY)
- Sombra vermelha ao redor do card
- Zoom sutil na imagem (scale 1.05)
**Efeito visual:**
1. Ao passar o mouse sobre um case
2. Overlay vermelho aparece cobrindo toda a área
3. Texto do resumo fica visível em branco
4. Card levanta 5px com sombra vermelha
5. Imagem dá zoom leve
### 5. **Elementos Atualizados com Vermelho**
#### Botões:
- `btn-primary`: Fundo vermelho, texto branco
- Hover: Tom mais escuro + levanta + sombra
- `btn-outline-primary`: Borda vermelha, preenche vermelho no hover
#### Badges/Tags:
- Fundo vermelho com texto branco
- Tags clicáveis nos cases
#### Ícones de Serviços:
- Círculos em vermelho (antes amarelo)
- Ícones brancos sobre vermelho
#### Timeline:
- Círculos da timeline em vermelho
- Ícones brancos
#### Seção de Contato:
- Fundo gradiente vermelho (C42127 → 8B1E23)
- Inputs com foco em vermelho
#### Detalhes do Case:
- Bordas de heading: vermelho
- Bordas de code blocks: vermelho
- Bordas de blockquotes: vermelho
- Links: vermelho
#### Masthead/Hero:
- Gradiente vermelho de fundo
#### Gradientes de Cases sem Imagem:
6 variações de gradientes vermelhos
### 6. **Arquivos Modificados**
```
✏️ /wwwroot/css/custom.css (reescrito completamente)
✏️ /Views/Shared/_Layout.cshtml (navbar light + logo otimizado)
✏️ /Views/Home/Index.cshtml (gradientes vermelhos)
✏️ /Views/Cases/Index.cshtml (gradientes vermelhos)
✏️ /Views/Cases/Details.cshtml (bordas vermelhas)
/wwwroot/img/logo-optimized.svg (novo logo)
```
## 🎨 Comparação Visual
### Antes (Amarelo):
- 🟡 Botões amarelos (#ffc800)
- 🟡 Hover amarelo nos cases
- 🟡 Timeline amarela
- ⚫ Navbar escuro
- 📦 Logo 745KB
### Depois (Vermelho):
- 🔴 Botões vermelhos (#C42127)
- 🔴 Hover vermelho cobrindo todo o case
- 🔴 Timeline vermelha
- ⚪ Navbar bege claro
- ✨ Logo 2KB transparente
## 🚀 Como Testar
### Se o app já está rodando:
1. Pare a aplicação (Ctrl+C no terminal)
2. Rode novamente: `dotnet run`
3. Abra: `http://localhost:5000`
### Se não está rodando:
```bash
cd aspnet/CarneiroTech
dotnet run
```
### Verificar mudanças:
1. **Homepage** - Veja os ícones vermelhos, botão vermelho
2. **Navbar** - Fundo claro, logo otimizado
3. **Cases** - Passe o mouse sobre um case → overlay vermelho
4. **Detalhes do Case** - Bordas vermelhas nos headings
5. **Contato** - Fundo vermelho na seção
## 📝 Notas Técnicas
### CSS Variables (custom.css):
```css
:root {
--primary-red: #C42127;
--dark-red: #8B1E23;
--accent-red: #E63946;
--light-bg: #FAF7F5;
--cream: #FFF8F0;
}
```
Todas as cores agora usam essas variáveis, facilitando ajustes futuros.
### Navbar Classes:
- Mudado de `navbar-dark` para `navbar-light`
- Adicionado estilo customizado para fundo claro
- Links com cor escura e hover vermelho
### Portfolio Hover:
```css
.portfolio-hover {
position: absolute;
width: 100%;
height: 100%;
background: rgba(196, 33, 39, 0.95);
/* Cobre todo o card */
}
```
## ✨ Próximos Passos (Opcional)
Se quiser fazer ajustes finos:
1. **Ajustar tom de vermelho:**
- Edite as variáveis CSS no topo de `custom.css`
- Todos os elementos atualizam automaticamente
2. **Mudar fundo do menu:**
- Edite `--light-bg` ou `--cream` em `custom.css`
- Exemplos de cores alternativas:
- `#F5F5DC` (bege mais escuro)
- `#FFFAF0` (creme floral)
- `#FDF5E6` (linho antigo)
3. **Logo personalizado:**
- Substitua `/wwwroot/img/logo-optimized.svg`
- Ou use seu logo original (se otimizar o SVG)
## 🐛 Troubleshooting
**Logo não aparece:**
- Verifique se `/wwwroot/img/logo-optimized.svg` existe
- Limpe o cache do browser (Ctrl+Shift+R)
**Cores não mudaram:**
- Limpe cache do browser
- Verifique se `custom.css` está sendo carregado (DevTools → Network)
**Build falha:**
- Se a app está rodando, pare primeiro
- Execute: `dotnet clean && dotnet build`
---
**Todas as mudanças solicitadas foram implementadas! ✅**
- ✅ Amarelo → Vermelho
- ✅ Logo com fundo transparente
- ✅ Menu com fundo claro (bege)
- ✅ Hover do portfolio corrigido (overlay vermelho cobrindo toda a área)

20
Models/CaseMetadata.cs Normal file
View File

@ -0,0 +1,20 @@
namespace CarneiroTech.Models;
public class CaseMetadata
{
public string Title { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string Client { get; set; } = string.Empty;
public string Industry { get; set; } = string.Empty;
public string Timeline { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
public List<string> Tags { get; set; } = new();
public bool Featured { get; set; }
public int Order { get; set; }
public DateTime Date { get; set; }
public string SeoTitle { get; set; } = string.Empty;
public string SeoDescription { get; set; } = string.Empty;
public string SeoKeywords { get; set; } = string.Empty;
}

8
Models/CaseModel.cs Normal file
View File

@ -0,0 +1,8 @@
namespace CarneiroTech.Models;
public class CaseModel
{
public CaseMetadata Metadata { get; set; } = new();
public string ContentHtml { get; set; } = string.Empty;
public string ContentMarkdown { get; set; } = string.Empty;
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace CarneiroTech.Models;
public class ContactFormModel
{
[Required(ErrorMessage = "Nome é obrigatório")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "Email é obrigatório")]
[EmailAddress(ErrorMessage = "Email inválido")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Telefone é obrigatório")]
[Phone(ErrorMessage = "Telefone inválido")]
public string Phone { get; set; } = string.Empty;
[Required(ErrorMessage = "Mensagem é obrigatória")]
public string Message { get; set; } = string.Empty;
}

8
Models/ErrorViewModel.cs Normal file
View File

@ -0,0 +1,8 @@
namespace CarneiroTech.Models;
public class ErrorViewModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

9
Models/SitemapItem.cs Normal file
View File

@ -0,0 +1,9 @@
namespace CarneiroTech.Models;
public class SitemapItem
{
public string Url { get; set; } = string.Empty;
public DateTime LastModified { get; set; }
public string ChangeFrequency { get; set; } = "weekly";
public decimal Priority { get; set; } = 0.5m;
}

36
Program.cs Normal file
View File

@ -0,0 +1,36 @@
using CarneiroTech.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddMemoryCache();
builder.Services.AddHttpContextAccessor();
// Register custom services
builder.Services.AddSingleton<IMarkdownService, MarkdownService>();
builder.Services.AddScoped<ICaseService, CaseService>();
builder.Services.AddScoped<ILanguageService, LanguageService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

View File

@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:32693",
"sslPort": 44347
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5203",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7177;http://localhost:5203",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

340
QUICK_START.md Normal file
View File

@ -0,0 +1,340 @@
# Quick Start Guide - Carneiro Tech Website
## Immediate Next Steps
### 1. Test Locally (5 minutes)
```bash
cd aspnet/CarneiroTech
dotnet run
```
Then open: `http://localhost:5000`
**What to check:**
- ✅ Homepage loads with all sections
- ✅ Click "Cases" in navbar → should show 3 cases
- ✅ Click on a case → should show full details
- ✅ Click on tags → should filter cases
- ✅ Sitemap at `/sitemap.xml` works
- ✅ Responsive design (resize browser)
---
### 2. Customize for Your Brand (30 minutes)
#### A. Update Logo Colors
Open `/wwwroot/css/custom.css` and update:
```css
/* Find and replace #ffc800 with your primary color */
/* Find and replace #667eea and #764ba2 with your gradient colors */
```
To extract colors from your logo:
1. Open `/logo/LogoNovo.svg` in a browser
2. Use browser DevTools to inspect
3. Copy hex color codes
4. Replace in custom.css
#### B. Update LinkedIn URL
Open `/Views/Shared/_Layout.cshtml` (line ~93):
```html
<a class="btn btn-dark btn-social mx-2"
href="https://linkedin.com/in/ricardo-carneiro" <!-- UPDATE THIS -->
```
#### C. Update Domain URLs
Find and replace `carneirotech.com` with your actual domain:
Files to update:
- `/Views/Shared/_Layout.cshtml` (lines 12, 16, 47, 48)
- `/Controllers/HomeController.cs` (sitemap URLs)
---
### 3. Add Your Own Cases (15 minutes per case)
#### Create New Case File
```bash
cd Content/Cases
# Create new file (use VSCode, Notepad, etc.)
touch my-new-case.md
```
#### Copy Template
Use one of the existing cases as template:
- `sap-integration-healthcare.md` (technical integration)
- `legacy-modernization.md` (architecture/migration)
- `mvp-definition.md` (consulting/strategy)
#### Fill Front Matter
```yaml
---
title: "Your Project Title"
slug: "your-project-slug" # Will be URL: /cases/your-project-slug
summary: "Short description for cards (2-3 lines)"
client: "Client Name or Confidential"
industry: "Industry"
timeline: "X months"
role: "Your Role"
image: "" # Leave empty for gradient or add /img/cases/yourimage.jpg
tags:
- Tag1
- Tag2
- Tag3
featured: true # Shows on homepage
order: 1 # Lower number = higher priority
date: 2024-01-15 # YYYY-MM-DD format
seo_title: "SEO Title (max 60 chars)"
seo_description: "SEO description (max 160 chars)"
seo_keywords: "keyword1, keyword2, keyword3"
---
```
#### Write Content
```markdown
## Overview
Brief overview...
---
## Challenge
What was the problem?
### Pain Points:
- Point 1
- Point 2
---
## Solution
How did you solve it?
\`\`\`csharp
// Code examples work
public void Example() {}
\`\`\`
---
## Results
Metrics and outcomes.
---
## Tech Stack
\`Tag1\` \`Tag2\` \`Tag3\`
---
[Call to action link](/#contact)
```
#### Preview
Restart app or wait 60min (cache expires):
```bash
dotnet run
# Navigate to: http://localhost:5000/cases/your-project-slug
```
---
### 4. Configure Email (30 minutes)
For the contact form to work, you need to implement email sending.
#### Option A: SendGrid (Recommended)
1. Sign up at sendgrid.com
2. Get API key
3. Add NuGet package:
```bash
dotnet add package SendGrid
```
4. Update `HomeController.cs` Contact method:
```csharp
// Replace TODO with SendGrid code
var apiKey = Configuration["SendGrid:ApiKey"];
var client = new SendGridClient(apiKey);
var from = new EmailAddress("noreply@carneirotech.com", "Carneiro Tech");
var subject = $"Novo contato: {model.Name}";
var to = new EmailAddress("ricardo@carneirotech.com");
var plainTextContent = model.Message;
var htmlContent = $"<strong>Nome:</strong> {model.Name}<br>...";
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
await client.SendEmailAsync(msg);
```
#### Option B: SMTP (Gmail, Outlook, etc.)
1. Add NuGet package:
```bash
dotnet add package MailKit
```
2. Configure SMTP settings in appsettings.json
3. Implement SMTP sending logic
---
### 5. Add Portfolio Images (Optional)
1. Create folder: `/wwwroot/img/cases/`
2. Add your images (jpg, png, webp)
3. Update case markdown front matter:
```yaml
image: "/img/cases/my-project.jpg"
```
**Recommended image size:** 800x600px (4:3 ratio)
---
### 6. Docker Deployment (Production)
#### Build
```bash
cd aspnet/CarneiroTech
docker build -t carneirotech:latest .
```
#### Run
```bash
docker run -d \
-p 80:80 \
-p 443:443 \
--name carneirotech \
-v $(pwd)/Content:/app/Content:ro \
carneirotech:latest
```
#### Or use Docker Compose
```bash
docker-compose up -d
```
**Access:** `http://localhost:8080`
---
## Common Issues & Solutions
### Issue: "404 Not Found" on case pages
**Solution:** Check slug in markdown matches URL
- Markdown: `slug: "my-case"`
- URL: `/cases/my-case` (must match exactly)
### Issue: Cases not appearing
**Solution:**
1. Check markdown file is in `Content/Cases/`
2. Verify front matter YAML is valid
3. Restart app (cache clears)
4. Check date format: `YYYY-MM-DD`
### Issue: Build fails
**Solution:**
```bash
dotnet clean
dotnet restore
dotnet build
```
### Issue: CSS not loading
**Solution:**
1. Check file exists: `/wwwroot/css/custom.css`
2. Verify `_Layout.cshtml` includes it
3. Clear browser cache (Ctrl+Shift+R)
### Issue: Logo not appearing
**Solution:**
1. Check file: `/wwwroot/img/logo.svg`
2. Verify navbar reference in `_Layout.cshtml`
3. Check image dimensions (logo should be ~40px height)
---
## Performance Tips
1. **Enable Response Caching**
- Add to `Program.cs`: `builder.Services.AddResponseCaching();`
- Add to pipeline: `app.UseResponseCaching();`
2. **Enable Response Compression**
- Add to `Program.cs`: `builder.Services.AddResponseCompression();`
3. **Optimize Images**
- Use WebP format
- Compress before uploading
- Tools: TinyPNG, Squoosh
4. **CDN for Static Assets**
- Upload `/wwwroot/` to CDN
- Update references in `_Layout.cshtml`
---
## Production Deployment Checklist
- [ ] Update all `carneirotech.com` URLs to your domain
- [ ] Configure SSL certificate (Let's Encrypt recommended)
- [ ] Set `ASPNETCORE_ENVIRONMENT=Production`
- [ ] Configure email sending (SendGrid or SMTP)
- [ ] Add Google Analytics (optional)
- [ ] Test all pages
- [ ] Test contact form
- [ ] Verify sitemap: `/sitemap.xml`
- [ ] Verify robots.txt: `/robots.txt`
- [ ] Test responsive design
- [ ] Run Lighthouse audit
- [ ] Set up monitoring (e.g., Application Insights)
- [ ] Configure backup for Content folder
- [ ] Test Docker deployment
- [ ] Set up CI/CD (optional)
---
## Resources
- **README.md** - Full documentation
- **IMPLEMENTATION_SUMMARY.md** - What was built
- **Example Cases** - 3 markdown examples in `Content/Cases/`
---
## Support
If you encounter issues:
1. Check the README.md for detailed documentation
2. Review IMPLEMENTATION_SUMMARY.md for architecture
3. Look at example cases for markdown syntax
4. Check Controller code for logic
---
**Ready to launch in ~2 hours** (including customizations)
Good luck! 🚀

303
README.md
View File

@ -1,2 +1,303 @@
# CarneiroTech # Carneiro Tech - Professional Consulting Website
Professional website for Carneiro Tech - Solution Design & Technical Consulting, built with ASP.NET MVC Core and Bootstrap.
## Features
- **Modern Design**: Agency Bootstrap template adapted for consulting
- **Markdown-based Cases**: Easy portfolio management with .md files
- **SEO Optimized**: Meta tags, Open Graph, Twitter Cards, JSON-LD structured data
- **Responsive**: Mobile-first design using Bootstrap 5
- **Tag Filtering**: Filter cases by technology/category
- **Sitemap**: Automatic XML sitemap generation
- **Docker Ready**: Containerized for easy deployment
## Tech Stack
### Backend
- ASP.NET MVC Core 8
- C# 12
- Markdig (Markdown parsing)
- YamlDotNet (YAML front matter)
### Frontend
- Bootstrap 5 (Agency template)
- Font Awesome icons
- Google Fonts (Montserrat, Roboto Slab)
### Deployment
- Docker & Docker Compose
- Ready for OCI (Oracle Cloud Infrastructure)
## Project Structure
```
CarneiroTech/
├── Controllers/
│ ├── HomeController.cs # Homepage, sitemap, contact
│ └── CasesController.cs # Cases list and details
├── Models/
│ ├── CaseModel.cs
│ ├── CaseMetadata.cs
│ ├── ContactFormModel.cs
│ └── SitemapItem.cs
├── Services/
│ ├── ICaseService.cs
│ ├── CaseService.cs
│ ├── IMarkdownService.cs
│ └── MarkdownService.cs
├── Views/
│ ├── Home/
│ │ └── Index.cshtml # Homepage
│ ├── Cases/
│ │ ├── Index.cshtml # Cases list
│ │ └── Details.cshtml # Individual case
│ └── Shared/
│ └── _Layout.cshtml # Main layout with SEO
├── Content/
│ └── Cases/ # Markdown case files
│ ├── sap-integration-healthcare.md
│ ├── legacy-modernization.md
│ └── mvp-definition.md
├── wwwroot/
│ ├── css/ # Bootstrap template CSS
│ ├── js/ # Bootstrap template JS
│ ├── img/ # Images and logo
│ └── robots.txt
├── Dockerfile
├── docker-compose.yml
└── README.md
```
## Getting Started
### Prerequisites
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
- [Docker](https://www.docker.com/get-started) (optional, for containerized deployment)
### Installation
1. **Clone the repository**
```bash
git clone <repository-url>
cd CarneiroTech
```
2. **Restore dependencies**
```bash
dotnet restore
```
3. **Run the application**
```bash
dotnet run
```
4. **Open in browser**
Navigate to: `http://localhost:5000` or `https://localhost:5001`
### Running with Docker
1. **Build and run with Docker Compose**
```bash
docker-compose up -d
```
2. **Access the application**
Navigate to: `http://localhost:8080`
3. **Stop the container**
```bash
docker-compose down
```
## Adding New Cases
### 1. Create a Markdown File
Create a new file in `Content/Cases/` folder:
```bash
touch Content/Cases/my-new-case.md
```
### 2. Add Front Matter + Content
```markdown
---
title: "My Amazing Project"
slug: "my-amazing-project"
summary: "Short summary for cards and SEO"
client: "Client Name"
industry: "Industry"
timeline: "3 months"
role: "Your Role"
image: "/img/cases/project.jpg"
tags:
- Tag1
- Tag2
- Tag3
featured: true
order: 1
date: 2024-01-15
seo_title: "SEO optimized title (max 60 chars)"
seo_description: "SEO description (max 160 chars)"
seo_keywords: "keyword1, keyword2, keyword3"
---
## Overview
Your case content here in Markdown format...
### Subsection
More content...
- Bullet points
- Work great
\`\`\`csharp
// Code blocks work too
public void Example() {
Console.WriteLine("Hello!");
}
\`\`\`
```
### 3. Refresh the Application
The case service uses in-memory caching (60 minutes). Either:
- Wait for cache expiration
- Restart the application
- Implement cache invalidation
### 4. Access Your Case
Navigate to: `/cases/my-amazing-project`
## Markdown Features
Supported Markdown features:
- **Headers** (H1-H6)
- **Bold**, *italic*, ~~strikethrough~~
- Lists (ordered and unordered)
- Links and images
- Code blocks with syntax highlighting
- Tables
- Blockquotes
- Horizontal rules
## SEO Features
### Meta Tags
- Dynamic title, description, keywords per page
- Canonical URLs
- Author meta tag
### Open Graph
- Full OG tags for social sharing
- Dynamic OG images per case
- Locale support (pt_BR)
### Twitter Cards
- Summary cards with large images
- Dynamic content per page
### Structured Data
- JSON-LD Organization schema
- Professional service markup
- Enhanced search results
### Sitemap
- Auto-generated XML sitemap
- Accessible at `/sitemap.xml`
- Includes homepage, cases index, and all individual cases
### Robots.txt
- Located at `/robots.txt`
- Allows all crawlers
- Points to sitemap
## Configuration
### Caching
Cases are cached in memory for 60 minutes. To adjust:
Edit `Services/CaseService.cs`:
```csharp
private const int CACHE_MINUTES = 60; // Change this value
```
### Site URL
Update the canonical URL and sitemap URLs in:
- `Views/Shared/_Layout.cshtml` (line 12)
- `Controllers/HomeController.cs` (Sitemap method)
Replace `https://carneirotech.com` with your domain.
## Deployment
### Docker Deployment
1. **Build the image**
```bash
docker build -t carneirotech:latest .
```
2. **Run the container**
```bash
docker run -d -p 8080:80 --name carneirotech carneirotech:latest
```
### Docker Compose Deployment
```bash
docker-compose up -d
```
### OCI (Oracle Cloud) Deployment
1. Push image to OCI Container Registry
2. Create Container Instance
3. Configure port mapping (80/443)
4. Set environment variables
5. Mount volume for Content folder (optional)
## Customization
### Logo
Replace `/wwwroot/img/logo.svg` with your logo.
Update navbar logo reference in `_Layout.cshtml` if needed.
### Colors
The template uses Bootstrap 5 with custom colors. To customize:
Edit `/wwwroot/css/styles.css`:
- Primary color: `#ffc800` (yellow/gold)
- Dark color: `#212529`
- Fonts: Montserrat, Roboto Slab
### Content
Edit the following views to customize content:
- `Views/Home/Index.cshtml` - Homepage content
- `Views/Shared/_Layout.cshtml` - Navigation, footer
- `Controllers/HomeController.cs` - Contact form logic
## License
This project is private and proprietary.
## Support
For issues or questions, contact: ricardo@carneirotech.com
---
**Built with ❤️ for Carneiro Tech**

99
Resources/SiteStrings.cs Normal file
View File

@ -0,0 +1,99 @@
namespace CarneiroTech.Resources
{
public class SiteStrings
{
public static Dictionary<string, Dictionary<string, string>> Translations = new()
{
// Navigation
["nav.services"] = new() { ["pt"] = "Serviços", ["en"] = "Services", ["es"] = "Servicios" },
["nav.cases"] = new() { ["pt"] = "Cases", ["en"] = "Cases", ["es"] = "Casos" },
["nav.about"] = new() { ["pt"] = "Sobre", ["en"] = "About", ["es"] = "Acerca de" },
["nav.contact"] = new() { ["pt"] = "Contato", ["en"] = "Contact", ["es"] = "Contacto" },
// Hero Section
["hero.greeting"] = new() { ["pt"] = "Bem-vindo ao Carneiro Tech", ["en"] = "Welcome to Carneiro Tech", ["es"] = "Bienvenido a Carneiro Tech" },
["hero.tagline"] = new() { ["pt"] = "É um prazer ter você aqui!", ["en"] = "It's A Great Pleasure To Have You Here!", ["es"] = "¡Es un placer tenerlo aquí!" },
["hero.title"] = new() { ["pt"] = "Solution Design & Consultoria Técnica", ["en"] = "Solution Design & Technical Consulting", ["es"] = "Solution Design y Consultoría Técnica" },
["hero.cta"] = new() { ["pt"] = "Saiba Mais", ["en"] = "Learn More", ["es"] = "Más Información" },
// Services Section
["services.title"] = new() { ["pt"] = "Serviços", ["en"] = "Services", ["es"] = "Servicios" },
["services.subtitle"] = new() { ["pt"] = "Transformando desafios técnicos em soluções eficientes", ["en"] = "Transforming technical challenges into efficient solutions", ["es"] = "Transformando desafíos técnicos en soluciones eficientes" },
["service.solution.title"] = new() { ["pt"] = "Solution Design", ["en"] = "Solution Design", ["es"] = "Solution Design" },
["service.solution.desc"] = new() { ["pt"] = "Desenho completo de soluções técnicas, identificando requisitos não explícitos e propondo arquiteturas adequadas ao contexto do projeto.", ["en"] = "Complete technical solution design, identifying non-explicit requirements and proposing architectures suitable for the project context.", ["es"] = "Diseño completo de soluciones técnicas, identificando requisitos no explícitos y proponiendo arquitecturas adecuadas al contexto del proyecto." },
["service.modernization.title"] = new() { ["pt"] = "Modernização de Sistemas", ["en"] = "System Modernization", ["es"] = "Modernización de Sistemas" },
["service.modernization.desc"] = new() { ["pt"] = "Migração de aplicações legadas para tecnologias modernas, com estratégias que garantem continuidade operacional e zero downtime.", ["en"] = "Migration of legacy applications to modern technologies, with strategies that ensure operational continuity and zero downtime.", ["es"] = "Migración de aplicaciones heredadas a tecnologías modernas, con estrategias que garantizan continuidad operativa y cero tiempo de inactividad." },
["service.architecture.title"] = new() { ["pt"] = "Arquitetura de Software", ["en"] = "Software Architecture", ["es"] = "Arquitectura de Software" },
["service.architecture.desc"] = new() { ["pt"] = "Definição de arquiteturas escaláveis, resilientes e adequadas ao contexto, considerando custos, prazos e manutenibilidade.", ["en"] = "Definition of scalable, resilient and context-appropriate architectures, considering costs, deadlines and maintainability.", ["es"] = "Definición de arquitecturas escalables, resilientes y adecuadas al contexto, considerando costos, plazos y mantenibilidad." },
["service.consulting.title"] = new() { ["pt"] = "Consultoria Técnica", ["en"] = "Technical Consulting", ["es"] = "Consultoría Técnica" },
["service.consulting.desc"] = new() { ["pt"] = "Análise de viabilidade técnica, otimização de processos, redução de custos operacionais e escolha de tecnologias adequadas.", ["en"] = "Technical feasibility analysis, process optimization, operational cost reduction and selection of appropriate technologies.", ["es"] = "Análisis de viabilidad técnica, optimización de procesos, reducción de costos operativos y selección de tecnologías adecuadas." },
// Portfolio/Cases Section
["portfolio.title"] = new() { ["pt"] = "Cases de Sucesso", ["en"] = "Success Cases", ["es"] = "Casos de Éxito" },
["portfolio.subtitle"] = new() { ["pt"] = "Conheça alguns projetos onde transformei desafios complexos em soluções elegantes", ["en"] = "Discover some projects where I transformed complex challenges into elegant solutions", ["es"] = "Conozca algunos proyectos donde transformé desafíos complejos en soluciones elegantes" },
["portfolio.viewcase"] = new() { ["pt"] = "Ver Case Completo", ["en"] = "View Full Case", ["es"] = "Ver Caso Completo" },
// About Section
["about.title"] = new() { ["pt"] = "Sobre", ["en"] = "About", ["es"] = "Acerca de" },
["about.subtitle"] = new() { ["pt"] = "Mais de uma década transformando desafios técnicos em soluções eficientes", ["en"] = "Over a decade transforming technical challenges into efficient solutions", ["es"] = "Más de una década transformando desafíos técnicos en soluciones eficientes" },
["about.text"] = new() {
["pt"] = "Com mais de 10 anos de experiência em engenharia de software, especializei-me em Solution Design, arquitetura de sistemas e modernização de aplicações legadas. Meu diferencial está em identificar requisitos não explícitos, antecipar problemas e propor soluções que realmente funcionam na prática.",
["en"] = "With over 10 years of experience in software engineering, I specialize in Solution Design, systems architecture and legacy application modernization. My differential is in identifying non-explicit requirements, anticipating problems and proposing solutions that really work in practice.",
["es"] = "Con más de 10 años de experiencia en ingeniería de software, me especializo en Solution Design, arquitectura de sistemas y modernización de aplicaciones heredadas. Mi diferencial está en identificar requisitos no explícitos, anticipar problemas y proponer soluciones que realmente funcionan en la práctica."
},
// Contact Section
["contact.title"] = new() { ["pt"] = "Contato", ["en"] = "Contact", ["es"] = "Contacto" },
["contact.subtitle"] = new() { ["pt"] = "Vamos conversar sobre seu desafio técnico", ["en"] = "Let's talk about your technical challenge", ["es"] = "Hablemos sobre su desafío técnico" },
["contact.name"] = new() { ["pt"] = "Seu Nome *", ["en"] = "Your Name *", ["es"] = "Su Nombre *" },
["contact.email"] = new() { ["pt"] = "Seu Email *", ["en"] = "Your Email *", ["es"] = "Su Email *" },
["contact.phone"] = new() { ["pt"] = "Seu Telefone", ["en"] = "Your Phone", ["es"] = "Su Teléfono" },
["contact.message"] = new() { ["pt"] = "Sua Mensagem *", ["en"] = "Your Message *", ["es"] = "Su Mensaje *" },
["contact.send"] = new() { ["pt"] = "Enviar Mensagem", ["en"] = "Send Message", ["es"] = "Enviar Mensaje" },
["contact.sending"] = new() { ["pt"] = "Enviando...", ["en"] = "Sending...", ["es"] = "Enviando..." },
["contact.success"] = new() { ["pt"] = "Mensagem enviada com sucesso! Entraremos em contato em breve.", ["en"] = "Message sent successfully! We'll get in touch soon.", ["es"] = "¡Mensaje enviado con éxito! Nos pondremos en contacto pronto." },
["contact.error"] = new() { ["pt"] = "Erro ao enviar mensagem. Por favor, tente novamente ou entre em contato via email.", ["en"] = "Error sending message. Please try again or contact via email.", ["es"] = "Error al enviar mensaje. Por favor, inténtelo de nuevo o contacte por email." },
["contact.or"] = new() { ["pt"] = "OU", ["en"] = "OR", ["es"] = "O" },
["contact.whatsapp"] = new() { ["pt"] = "Falar via WhatsApp", ["en"] = "Chat on WhatsApp", ["es"] = "Hablar por WhatsApp" },
["contact.whatsapp.subtitle"] = new() { ["pt"] = "Resposta rápida e direta", ["en"] = "Quick and direct response", ["es"] = "Respuesta rápida y directa" },
// Footer
["footer.rights"] = new() { ["pt"] = "Carneiro Tech - Todos os direitos reservados", ["en"] = "Carneiro Tech - All rights reserved", ["es"] = "Carneiro Tech - Todos los derechos reservados" },
["footer.privacy"] = new() { ["pt"] = "Privacidade", ["en"] = "Privacy", ["es"] = "Privacidad" },
["footer.terms"] = new() { ["pt"] = "Termos", ["en"] = "Terms", ["es"] = "Términos" },
// Case Details
["case.client"] = new() { ["pt"] = "Cliente", ["en"] = "Client", ["es"] = "Cliente" },
["case.industry"] = new() { ["pt"] = "Indústria", ["en"] = "Industry", ["es"] = "Industria" },
["case.timeline"] = new() { ["pt"] = "Prazo", ["en"] = "Timeline", ["es"] = "Plazo" },
["case.role"] = new() { ["pt"] = "Papel", ["en"] = "Role", ["es"] = "Rol" },
["case.tags"] = new() { ["pt"] = "Tecnologias", ["en"] = "Technologies", ["es"] = "Tecnologías" },
["case.back"] = new() { ["pt"] = "← Voltar para Cases", ["en"] = "← Back to Cases", ["es"] = "← Volver a Casos" },
// WhatsApp message
["whatsapp.message"] = new() {
["pt"] = "[Site] Olá! Eu gostaria de conversar com você sobre uma possível proposta comercial.",
["en"] = "[Website] Hello! I would like to talk to you about a possible business proposal.",
["es"] = "[Sitio] ¡Hola! Me gustaría hablar con usted sobre una posible propuesta comercial."
},
};
public static string Get(string key, string language)
{
if (Translations.TryGetValue(key, out var translations))
{
if (translations.TryGetValue(language, out var text))
{
return text;
}
// Fallback to Portuguese if translation not found
return translations.GetValueOrDefault("pt", key);
}
return key; // Return key if not found at all
}
}
}

208
Services/CaseService.cs Normal file
View File

@ -0,0 +1,208 @@
using CarneiroTech.Models;
using Microsoft.Extensions.Caching.Memory;
using System.Globalization;
namespace CarneiroTech.Services;
public class CaseService : ICaseService
{
private readonly IMarkdownService _markdownService;
private readonly IMemoryCache _cache;
private readonly IWebHostEnvironment _environment;
private readonly ILanguageService _languageService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly string _casesBasePath;
private const string CACHE_KEY_PREFIX = "cases_";
private const int CACHE_MINUTES = 60;
public CaseService(IMarkdownService markdownService, IMemoryCache cache, IWebHostEnvironment environment,
ILanguageService languageService, IHttpContextAccessor httpContextAccessor)
{
_markdownService = markdownService;
_cache = cache;
_environment = environment;
_languageService = languageService;
_httpContextAccessor = httpContextAccessor;
_casesBasePath = Path.Combine(_environment.ContentRootPath, "Content", "Cases");
}
private string GetCurrentLanguage()
{
if (_httpContextAccessor.HttpContext != null)
{
return _languageService.GetCurrentLanguage(_httpContextAccessor.HttpContext);
}
return "pt"; // Default fallback
}
private string GetCasesPath(string language)
{
return Path.Combine(_casesBasePath, language);
}
public async Task<List<CaseModel>> GetAllCasesAsync()
{
var language = GetCurrentLanguage();
var cacheKey = $"{CACHE_KEY_PREFIX}{language}";
if (_cache.TryGetValue(cacheKey, out List<CaseModel>? cachedCases) && cachedCases != null)
{
return cachedCases;
}
var cases = new List<CaseModel>();
var casesPath = GetCasesPath(language);
if (!Directory.Exists(casesPath))
{
// Try Portuguese as fallback
casesPath = GetCasesPath("pt");
if (!Directory.Exists(casesPath))
{
Directory.CreateDirectory(casesPath);
return cases;
}
}
var markdownFiles = Directory.GetFiles(casesPath, "*.md");
foreach (var file in markdownFiles)
{
var caseModel = await ParseCaseFileAsync(file);
if (caseModel != null)
{
cases.Add(caseModel);
}
}
// Sort by order (ascending) and date (descending)
cases = cases.OrderBy(c => c.Metadata.Order)
.ThenByDescending(c => c.Metadata.Date)
.ToList();
_cache.Set(cacheKey, cases, TimeSpan.FromMinutes(CACHE_MINUTES));
return cases;
}
public async Task<List<CaseModel>> GetFeaturedCasesAsync()
{
var allCases = await GetAllCasesAsync();
return allCases.Where(c => c.Metadata.Featured).ToList();
}
public async Task<CaseModel?> GetCaseBySlugAsync(string slug)
{
var allCases = await GetAllCasesAsync();
return allCases.FirstOrDefault(c => c.Metadata.Slug.Equals(slug, StringComparison.OrdinalIgnoreCase));
}
public async Task<List<string>> GetAllTagsAsync()
{
var allCases = await GetAllCasesAsync();
var tags = allCases.SelectMany(c => c.Metadata.Tags)
.Distinct()
.OrderBy(t => t)
.ToList();
return tags;
}
public async Task<List<CaseModel>> GetCasesByTagAsync(string tag)
{
var allCases = await GetAllCasesAsync();
return allCases.Where(c => c.Metadata.Tags.Contains(tag, StringComparer.OrdinalIgnoreCase))
.ToList();
}
private async Task<CaseModel?> ParseCaseFileAsync(string filePath)
{
try
{
var content = await File.ReadAllTextAsync(filePath);
var frontMatter = _markdownService.ParseFrontMatter(content, out string bodyContent);
var metadata = new CaseMetadata
{
Title = GetStringValue(frontMatter, "title"),
Slug = GetStringValue(frontMatter, "slug"),
Summary = GetStringValue(frontMatter, "summary"),
Client = GetStringValue(frontMatter, "client"),
Industry = GetStringValue(frontMatter, "industry"),
Timeline = GetStringValue(frontMatter, "timeline"),
Role = GetStringValue(frontMatter, "role"),
Image = GetStringValue(frontMatter, "image"),
Tags = GetListValue(frontMatter, "tags"),
Featured = GetBoolValue(frontMatter, "featured"),
Order = GetIntValue(frontMatter, "order"),
Date = GetDateValue(frontMatter, "date"),
SeoTitle = GetStringValue(frontMatter, "seo_title"),
SeoDescription = GetStringValue(frontMatter, "seo_description"),
SeoKeywords = GetStringValue(frontMatter, "seo_keywords")
};
var htmlContent = _markdownService.ConvertToHtml(bodyContent);
return new CaseModel
{
Metadata = metadata,
ContentHtml = htmlContent,
ContentMarkdown = bodyContent
};
}
catch (Exception)
{
// Log error here if needed
return null;
}
}
private string GetStringValue(Dictionary<string, object> dict, string key)
{
return dict.ContainsKey(key) ? dict[key]?.ToString() ?? string.Empty : string.Empty;
}
private List<string> GetListValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return new List<string>();
if (dict[key] is List<object> list)
{
return list.Select(item => item?.ToString() ?? string.Empty).ToList();
}
return new List<string>();
}
private bool GetBoolValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return false;
var value = dict[key]?.ToString()?.ToLower();
return value == "true" || value == "yes" || value == "1";
}
private int GetIntValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return 0;
if (int.TryParse(dict[key]?.ToString(), out int result))
{
return result;
}
return 0;
}
private DateTime GetDateValue(Dictionary<string, object> dict, string key)
{
if (!dict.ContainsKey(key)) return DateTime.MinValue;
var dateString = dict[key]?.ToString();
if (DateTime.TryParse(dateString, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime result))
{
return result;
}
return DateTime.MinValue;
}
}

12
Services/ICaseService.cs Normal file
View File

@ -0,0 +1,12 @@
using CarneiroTech.Models;
namespace CarneiroTech.Services;
public interface ICaseService
{
Task<List<CaseModel>> GetAllCasesAsync();
Task<List<CaseModel>> GetFeaturedCasesAsync();
Task<CaseModel?> GetCaseBySlugAsync(string slug);
Task<List<string>> GetAllTagsAsync();
Task<List<CaseModel>> GetCasesByTagAsync(string tag);
}

View File

@ -0,0 +1,7 @@
namespace CarneiroTech.Services;
public interface IMarkdownService
{
string ConvertToHtml(string markdown);
Dictionary<string, object> ParseFrontMatter(string content, out string bodyContent);
}

View File

@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Http;
using System.Globalization;
namespace CarneiroTech.Services
{
public interface ILanguageService
{
string GetCurrentLanguage(HttpContext context);
void SetLanguage(HttpContext context, string language);
string DetectBrowserLanguage(HttpContext context);
}
public class LanguageService : ILanguageService
{
private const string LanguageCookieName = "CarneiroTech_Language";
private readonly string[] _supportedLanguages = { "pt", "en", "es" };
private const string DefaultLanguage = "pt";
public string GetCurrentLanguage(HttpContext context)
{
// 1. Check cookie first (user preference)
if (context.Request.Cookies.TryGetValue(LanguageCookieName, out var cookieLanguage))
{
if (_supportedLanguages.Contains(cookieLanguage))
{
return cookieLanguage;
}
}
// 2. Detect from browser Accept-Language header
var browserLanguage = DetectBrowserLanguage(context);
if (!string.IsNullOrEmpty(browserLanguage))
{
// Save detected language to cookie
SetLanguage(context, browserLanguage);
return browserLanguage;
}
// 3. Fallback to default (Portuguese)
return DefaultLanguage;
}
public void SetLanguage(HttpContext context, string language)
{
if (_supportedLanguages.Contains(language))
{
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
HttpOnly = false, // Allow JavaScript to read for client-side logic
Secure = true,
SameSite = SameSiteMode.Lax,
Path = "/"
};
context.Response.Cookies.Append(LanguageCookieName, language, cookieOptions);
}
}
public string DetectBrowserLanguage(HttpContext context)
{
var acceptLanguageHeader = context.Request.Headers["Accept-Language"].ToString();
if (string.IsNullOrEmpty(acceptLanguageHeader))
{
return DefaultLanguage;
}
// Parse Accept-Language header
// Format: "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6"
var browserLanguages = acceptLanguageHeader
.Split(',')
.Select(lang => lang.Split(';')[0].Trim())
.Select(lang => lang.Split('-')[0].ToLower()) // Get only language code (pt from pt-BR)
.Distinct()
.ToList();
// Find first supported language
foreach (var browserLang in browserLanguages)
{
if (_supportedLanguages.Contains(browserLang))
{
return browserLang;
}
}
return DefaultLanguage;
}
}
}

View File

@ -0,0 +1,76 @@
using Markdig;
using YamlDotNet.Serialization;
namespace CarneiroTech.Services;
public class MarkdownService : IMarkdownService
{
private readonly MarkdownPipeline _pipeline;
public MarkdownService()
{
_pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
}
public string ConvertToHtml(string markdown)
{
return Markdown.ToHtml(markdown, _pipeline);
}
public Dictionary<string, object> ParseFrontMatter(string content, out string bodyContent)
{
bodyContent = content;
var frontMatter = new Dictionary<string, object>();
if (!content.StartsWith("---"))
{
return frontMatter;
}
var lines = content.Split('\n');
var yamlLines = new List<string>();
var bodyLines = new List<string>();
var inFrontMatter = false;
var frontMatterEnded = false;
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (i == 0 && line.Trim() == "---")
{
inFrontMatter = true;
continue;
}
if (inFrontMatter && line.Trim() == "---")
{
inFrontMatter = false;
frontMatterEnded = true;
continue;
}
if (inFrontMatter)
{
yamlLines.Add(line);
}
else if (frontMatterEnded)
{
bodyLines.Add(line);
}
}
if (yamlLines.Any())
{
var yaml = string.Join("\n", yamlLines);
var deserializer = new DeserializerBuilder().Build();
frontMatter = deserializer.Deserialize<Dictionary<string, object>>(yaml)
?? new Dictionary<string, object>();
}
bodyContent = string.Join("\n", bodyLines).Trim();
return frontMatter;
}
}

184
Views/Cases/Details.cshtml Normal file
View File

@ -0,0 +1,184 @@
@model CarneiroTech.Models.CaseModel
<!-- Case Detail Header -->
<section class="page-section" style="padding-top: 150px;">
<div class="container">
<!-- Header -->
<div class="row mb-5">
<div class="col-lg-12 text-center">
<h1 class="display-4 mb-4">@Model.Metadata.Title</h1>
<p class="lead text-muted mb-4">@Model.Metadata.Summary</p>
<!-- Meta Information -->
<div class="row justify-content-center mb-4">
@if (!string.IsNullOrEmpty(Model.Metadata.Client))
{
<div class="col-md-3 mb-2">
<strong>Cliente:</strong> @Model.Metadata.Client
</div>
}
@if (!string.IsNullOrEmpty(Model.Metadata.Industry))
{
<div class="col-md-3 mb-2">
<strong>Indústria:</strong> @Model.Metadata.Industry
</div>
}
@if (!string.IsNullOrEmpty(Model.Metadata.Timeline))
{
<div class="col-md-3 mb-2">
<strong>Timeline:</strong> @Model.Metadata.Timeline
</div>
}
@if (!string.IsNullOrEmpty(Model.Metadata.Role))
{
<div class="col-md-3 mb-2">
<strong>Meu Role:</strong> @Model.Metadata.Role
</div>
}
</div>
<!-- Tags -->
@if (Model.Metadata.Tags.Any())
{
<div class="tags mt-3 mb-4">
@foreach (var tag in Model.Metadata.Tags)
{
<a href="/cases?tag=@tag" class="badge bg-primary me-2 mb-2 text-decoration-none" style="font-size: 0.9rem; padding: 0.5rem 1rem;">@tag</a>
}
</div>
}
</div>
</div>
<!-- Content -->
<div class="row">
<div class="col-lg-10 mx-auto">
<div class="case-content">
@Html.Raw(Model.ContentHtml)
</div>
</div>
</div>
<!-- Navigation -->
<div class="row mt-5">
<div class="col-lg-12 text-center">
<a href="/cases" class="btn btn-outline-primary btn-lg">
<i class="fas fa-arrow-left me-2"></i>Ver Todos os Cases
</a>
<a href="/#contact" class="btn btn-primary btn-lg ms-2">
<i class="fas fa-envelope me-2"></i>Vamos Conversar
</a>
</div>
</div>
</div>
</section>
@section Scripts {
<!-- Syntax highlighting for code blocks (optional) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<style>
.case-content {
font-size: 1.1rem;
line-height: 1.8;
}
.case-content h2 {
margin-top: 2.5rem;
margin-bottom: 1.5rem;
font-weight: bold;
border-bottom: 2px solid #C42127;
padding-bottom: 0.5rem;
}
.case-content h3 {
margin-top: 2rem;
margin-bottom: 1rem;
font-weight: 600;
}
.case-content h4 {
margin-top: 1.5rem;
margin-bottom: 1rem;
font-weight: 600;
color: #212529;
}
.case-content p {
margin-bottom: 1.2rem;
}
.case-content ul,
.case-content ol {
margin-bottom: 1.5rem;
padding-left: 2rem;
}
.case-content li {
margin-bottom: 0.5rem;
}
.case-content pre {
background-color: #f8f9fa;
border-left: 4px solid #C42127;
padding: 1.5rem;
margin: 1.5rem 0;
border-radius: 0.375rem;
overflow-x: auto;
}
.case-content code {
background-color: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.9em;
color: #e83e8c;
}
.case-content pre code {
background-color: transparent;
padding: 0;
color: inherit;
}
.case-content blockquote {
border-left: 4px solid #C42127;
padding-left: 1.5rem;
margin: 1.5rem 0;
font-style: italic;
color: #6c757d;
}
.case-content img {
max-width: 100%;
height: auto;
margin: 2rem 0;
border-radius: 0.375rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.case-content table {
width: 100%;
margin: 1.5rem 0;
border-collapse: collapse;
}
.case-content table th,
.case-content table td {
padding: 0.75rem;
border: 1px solid #dee2e6;
}
.case-content table th {
background-color: #f8f9fa;
font-weight: 600;
}
.case-content hr {
margin: 2rem 0;
border-top: 2px solid #dee2e6;
}
</style>
}

91
Views/Cases/Index.cshtml Normal file
View File

@ -0,0 +1,91 @@
@model List<CarneiroTech.Models.CaseModel>
@{
var selectedTag = ViewData["SelectedTag"] as string;
var allTags = ViewBag.AllTags as List<string> ?? new List<string>();
}
<!-- Page Header-->
<header class="masthead" style="padding: 150px 0 100px;">
<div class="container">
<div class="masthead-subheading">Portfolio</div>
<div class="masthead-heading text-uppercase">Nossos Cases</div>
</div>
</header>
<!-- Cases List-->
<section class="page-section bg-light">
<div class="container">
<!-- Tags Filter -->
@if (allTags.Any())
{
<div class="row mb-5">
<div class="col-12 text-center">
<h5 class="mb-3">Filtrar por tecnologia:</h5>
<div class="btn-group-wrap">
<a href="/cases" class="btn @(string.IsNullOrEmpty(selectedTag) ? "btn-primary" : "btn-outline-primary") m-1">Todos</a>
@foreach (var tag in allTags)
{
<a href="/cases?tag=@tag" class="btn @(selectedTag == tag ? "btn-primary" : "btn-outline-primary") m-1">@tag</a>
}
</div>
</div>
</div>
}
<!-- Cases Grid -->
<div class="row">
@if (Model != null && Model.Any())
{
foreach (var caseItem in Model)
{
<div class="col-lg-4 col-sm-6 mb-4">
<!-- Portfolio item -->
<div class="portfolio-item">
<a class="portfolio-link" href="/cases/@caseItem.Metadata.Slug">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<p class="text-white fw-bold px-3">@caseItem.Metadata.Summary</p>
</div>
</div>
@if (!string.IsNullOrEmpty(caseItem.Metadata.Image))
{
<img class="img-fluid" src="@caseItem.Metadata.Image" alt="@caseItem.Metadata.Title" />
}
else
{
<div style="height: 300px; background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);"></div>
}
</a>
<div class="portfolio-caption">
<div class="portfolio-caption-heading">@caseItem.Metadata.Title</div>
<div class="portfolio-caption-subheading text-muted">@caseItem.Metadata.Industry</div>
<div class="mt-2">
@foreach (var tag in caseItem.Metadata.Tags.Take(3))
{
<span class="badge bg-secondary me-1">@tag</span>
}
</div>
</div>
</div>
</div>
}
}
else
{
<div class="col-12 text-center">
<p class="lead text-muted">
@if (!string.IsNullOrEmpty(selectedTag))
{
<span>Nenhum case encontrado para a tag "@selectedTag".</span>
}
else
{
<span>Em breve, novos cases serão adicionados.</span>
}
</p>
<a href="/cases" class="btn btn-primary">Ver Todos os Cases</a>
</div>
}
</div>
</div>
</section>

302
Views/Home/Index.cshtml Normal file
View File

@ -0,0 +1,302 @@
@model List<CarneiroTech.Models.CaseModel>
@{
ViewData["Title"] = ViewData["Title"] ?? "Carneiro Tech - Solution Design & Technical Consulting";
}
<!-- Masthead-->
<header class="masthead">
<div class="container">
<div class="masthead-subheading">Solution Design & Technical Consulting</div>
<div class="masthead-heading text-uppercase">Conectando Negócio e Tecnologia</div>
<a class="btn btn-primary btn-xl text-uppercase" href="#services">Saiba Mais</a>
</div>
</header>
<!-- Services-->
<section class="page-section" id="services">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Serviços</h2>
<h3 class="section-subheading text-muted">20+ anos conectando negócio e tecnologia</h3>
</div>
<div class="row text-center">
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-lightbulb fa-stack-1x fa-inverse"></i>
</span>
<h4 class="my-3">Solution Design</h4>
<p class="text-muted">Desenho de soluções técnicas que conectam objetivos de negócio com arquitetura de sistemas. Experiência com integrações SAP, arquiteturas enterprise e modernização de legados.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-clipboard-check fa-stack-1x fa-inverse"></i>
</span>
<h4 class="my-3">Technical Consulting</h4>
<p class="text-muted">Assessoria técnica para tomada de decisão: definição de MVP, priorização de backlog, análise de viabilidade técnica e due diligence de produtos digitais.</p>
</div>
<div class="col-md-4">
<span class="fa-stack fa-4x">
<i class="fas fa-circle fa-stack-2x text-primary"></i>
<i class="fas fa-code fa-stack-1x fa-inverse"></i>
</span>
<h4 class="my-3">Technical Proposals</h4>
<p class="text-muted">Elaboração de propostas técnicas detalhadas: estimativas, arquitetura, tecnologias, riscos e roadmap de implementação para projetos complexos.</p>
</div>
</div>
</div>
</section>
<!-- Portfolio Grid (Cases)-->
<section class="page-section bg-light" id="portfolio">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Cases</h2>
<h3 class="section-subheading text-muted">Projetos que transformaram negócios</h3>
</div>
<div class="row">
@if (Model != null && Model.Any())
{
foreach (var caseItem in Model.Take(6))
{
<div class="col-lg-4 col-sm-6 mb-4">
<!-- Portfolio item -->
<div class="portfolio-item">
<a class="portfolio-link" href="/cases/@caseItem.Metadata.Slug">
<div class="portfolio-hover">
<div class="portfolio-hover-content">
<p class="text-white fw-bold">@caseItem.Metadata.Summary</p>
</div>
</div>
@if (!string.IsNullOrEmpty(caseItem.Metadata.Image))
{
<img class="img-fluid" src="@caseItem.Metadata.Image" alt="@caseItem.Metadata.Title" />
}
else
{
<div style="height: 300px; background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);"></div>
}
</a>
<div class="portfolio-caption">
<div class="portfolio-caption-heading">@caseItem.Metadata.Title</div>
<div class="portfolio-caption-subheading text-muted">@caseItem.Metadata.Industry</div>
</div>
</div>
</div>
}
}
else
{
<div class="col-12 text-center">
<p class="text-muted">Em breve, novos cases serão adicionados.</p>
</div>
}
</div>
<div class="text-center mt-4">
<a class="btn btn-primary btn-xl text-uppercase" href="/cases">Ver Todos os Cases</a>
</div>
</div>
</section>
<!-- About-->
<section class="page-section" id="about">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Sobre</h2>
<h3 class="section-subheading text-muted">Ricardo Carneiro - Carneiro Tech</h3>
</div>
<ul class="timeline">
<li>
<div class="timeline-image"><i class="fas fa-graduation-cap fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2000-2005</h4>
<h4 class="subheading">Início da Jornada</h4>
</div>
<div class="timeline-body"><p class="text-muted">Primeiros passos na tecnologia: desenvolvimento web, banco de dados e sistemas corporativos. Formação sólida em Engenharia de Software.</p></div>
</div>
</li>
<li class="timeline-inverted">
<div class="timeline-image"><i class="fas fa-building fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2005-2015</h4>
<h4 class="subheading">Enterprise & SAP</h4>
</div>
<div class="timeline-body"><p class="text-muted">Especialização em integrações SAP e arquiteturas enterprise. Projetos em multinacionais nos setores healthcare, varejo e manufatura.</p></div>
</div>
</li>
<li>
<div class="timeline-image"><i class="fas fa-rocket fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2015-2020</h4>
<h4 class="subheading">Digital Transformation</h4>
</div>
<div class="timeline-body"><p class="text-muted">Liderança técnica em transformação digital: cloud migration, modernização de legados e implementação de metodologias ágeis.</p></div>
</div>
</li>
<li class="timeline-inverted">
<div class="timeline-image"><i class="fas fa-handshake fa-3x text-white"></i></div>
<div class="timeline-panel">
<div class="timeline-heading">
<h4>2020-Hoje</h4>
<h4 class="subheading">Consultoria Independente</h4>
</div>
<div class="timeline-body"><p class="text-muted">Fundação da Carneiro Tech: consultoria especializada em Solution Design, Technical Proposals e Due Diligence para empresas de diversos portes.</p></div>
</div>
</li>
<li class="timeline-inverted">
<div class="timeline-image">
<h4 style="vertical-align:central">
Vamos
<br />
Trabalhar
<br />
Juntos!
</h4>
</div>
</li>
</ul>
</div>
</section>
<!-- Contact-->
<section class="page-section" id="contact">
<div class="container">
<div class="text-center">
<h2 class="section-heading text-uppercase">Contato</h2>
<h3 class="section-subheading text-white">Vamos conversar sobre seu desafio técnico</h3>
</div>
<!-- Alert messages -->
<div id="contactAlert" class="alert text-center" style="display: none;"></div>
<form id="contactForm">
<div class="row align-items-stretch mb-5">
<div class="col-md-6">
<div class="form-group mb-3">
<input class="form-control" id="contactName" name="name" type="text" placeholder="Seu Nome *" required />
</div>
<div class="form-group mb-3">
<input class="form-control" id="contactEmail" name="email" type="email" placeholder="Seu Email *" required />
</div>
<div class="form-group mb-md-0">
<input class="form-control" id="contactPhone" name="phone" type="tel" placeholder="Seu Telefone" />
</div>
</div>
<div class="col-md-6">
<div class="form-group form-group-textarea mb-md-0">
<textarea class="form-control" id="contactMessage" name="message" placeholder="Sua Mensagem *" style="height: 100%;" required></textarea>
</div>
</div>
</div>
<div class="text-center">
<button class="btn btn-primary btn-xl text-uppercase" id="submitButton" type="submit">
<span id="buttonText">Enviar Mensagem</span>
<span id="buttonSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;" role="status" aria-hidden="true"></span>
</button>
</div>
</form>
<!-- WhatsApp Contact Option -->
<div class="text-center mt-5">
<div class="mb-3">
<span class="text-white-50" style="font-size: 1.1rem;">OU</span>
</div>
<a href="https://wa.me/5511970792602?text=%5BSite%5D%20Ol%C3%A1%21%20Eu%20gostaria%20de%20conversar%20com%20voc%C3%AA%20sobre%20uma%20poss%C3%ADvel%20proposta%20comercial."
class="btn btn-success btn-xl text-uppercase"
target="_blank"
style="background-color: #25D366; border-color: #25D366;">
<i class="fab fa-whatsapp me-2"></i>
Falar via WhatsApp
</a>
<div class="mt-2">
<small class="text-white-50">Resposta rápida e direta</small>
</div>
</div>
</div>
</section>
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
e.preventDefault();
const form = this;
const submitButton = document.getElementById('submitButton');
const buttonText = document.getElementById('buttonText');
const buttonSpinner = document.getElementById('buttonSpinner');
const alertDiv = document.getElementById('contactAlert');
// Get form data
const formData = new FormData(form);
const data = {
name: formData.get('name'),
email: formData.get('email'),
phone: formData.get('phone') || '',
message: formData.get('message')
};
console.log('Sending form data:', data);
// Show loading state
submitButton.disabled = true;
buttonText.textContent = 'Enviando...';
buttonSpinner.style.display = 'inline-block';
alertDiv.style.display = 'none';
// Create form and submit via POST (Google Apps Script accepts form data)
const scriptUrl = 'https://script.google.com/macros/s/AKfycbwYIzI1TGYEKYmkUhhdvNDQcrpInwNJ9Olk24KLZYEb4AycMaGao2qbfk2gmPsp9yUZ4A/exec';
// Create a hidden iframe to submit the form
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.name = 'contact-frame';
document.body.appendChild(iframe);
// Create a temporary form
const tempForm = document.createElement('form');
tempForm.method = 'POST';
tempForm.action = scriptUrl;
tempForm.target = 'contact-frame';
// Add form fields
Object.keys(data).forEach(key => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = data[key];
tempForm.appendChild(input);
});
// Add to document and submit
document.body.appendChild(tempForm);
// Set timeout to show success message
const timeout = setTimeout(() => {
console.log('Form submitted successfully (timeout)');
// Success - assume it worked after 2 seconds
alertDiv.className = 'alert alert-success text-center';
alertDiv.textContent = 'Mensagem enviada com sucesso! Entraremos em contato em breve.';
alertDiv.style.display = 'block';
form.reset();
// Scroll to alert
alertDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Reset button state
submitButton.disabled = false;
buttonText.textContent = 'Enviar Mensagem';
buttonSpinner.style.display = 'none';
// Cleanup
document.body.removeChild(tempForm);
document.body.removeChild(iframe);
}, 2000);
// Submit the form
tempForm.submit();
});
</script>

View File

@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

25
Views/Shared/Error.cshtml Normal file
View File

@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

156
Views/Shared/_Layout.cshtml Normal file
View File

@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- SEO Meta Tags -->
<title>@ViewData["Title"]</title>
<meta name="description" content="@ViewData["Description"]" />
<meta name="keywords" content="@ViewData["Keywords"]" />
<meta name="author" content="Ricardo Carneiro - Carneiro Tech" />
<link rel="canonical" href="@($"https://carneirotech.com{Context.Request.Path}")" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:url" content="@($"https://carneirotech.com{Context.Request.Path}")" />
<meta property="og:title" content="@ViewData["Title"]" />
<meta property="og:description" content="@ViewData["Description"]" />
<meta property="og:image" content="@(ViewData["OgImage"] ?? "https://carneirotech.com/img/logo.svg")" />
<meta property="og:locale" content="pt_BR" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="@ViewData["Title"]" />
<meta name="twitter:description" content="@ViewData["Description"]" />
<meta name="twitter:image" content="@(ViewData["OgImage"] ?? "https://carneirotech.com/img/logo.svg")" />
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="~/favicon.ico" />
<!-- Font Awesome icons (free version)-->
<script src="https://use.fontawesome.com/releases/v6.3.0/js/all.js" crossorigin="anonymous"></script>
<!-- Google fonts-->
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com/css?family=Roboto+Slab:400,100,300,700" rel="stylesheet" type="text/css" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="~/css/styles.css" rel="stylesheet" />
<!-- Custom CSS -->
<link href="~/css/custom.css" rel="stylesheet" />
<!-- JSON-LD Structured Data -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "ProfessionalService",
"name": "Carneiro Tech",
"description": "Solution Design & Technical Consulting",
"url": "https://carneirotech.com",
"logo": "https://carneirotech.com/img/logo.svg",
"image": "https://carneirotech.com/img/logo.svg",
"priceRange": "$$",
"address": {
"@@type": "PostalAddress",
"addressLocality": "São Bernardo do Campo",
"addressRegion": "SP",
"addressCountry": "BR"
},
"sameAs": [
"https://linkedin.com/in/ricardo-carneiro"
]
}
</script>
<!-- Microsoft Clarity -->
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "up7yoisy52");
</script>
</head>
<body id="page-top">
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-light fixed-top" id="mainNav">
<div class="container">
<a class="navbar-brand" href="/">
<img src="~/img/LogoPequeno.png" alt="Carneiro Tech" style="height: 45px;" />
<span style="color: #8B1E23">Carneiro Tech</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
Menu
<i class="fas fa-bars ms-1"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
<li class="nav-item"><a class="nav-link" href="/#services">Serviços</a></li>
<li class="nav-item"><a class="nav-link" href="/cases">Cases</a></li>
<li class="nav-item"><a class="nav-link" href="/#about">Sobre</a></li>
<li class="nav-item"><a class="nav-link" href="/#contact">Contato</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="languageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-globe"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languageDropdown">
<li><a class="dropdown-item" href="#" onclick="changeLanguage('pt'); return false;">🇧🇷 Português</a></li>
<li><a class="dropdown-item" href="#" onclick="changeLanguage('en'); return false;">🇺🇸 English</a></li>
<li><a class="dropdown-item" href="#" onclick="changeLanguage('es'); return false;">🇪🇸 Español</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Main Content -->
@RenderBody()
<!-- Footer -->
<footer class="footer py-4">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-4 text-lg-start">Copyright &copy; @DateTime.Now.Year Carneiro Tech - Ricardo Carneiro</div>
<div class="col-lg-4 my-3 my-lg-0">
<a class="btn btn-dark btn-social mx-2" href="https://linkedin.com/in/ricardo-carneiro" aria-label="LinkedIn" target="_blank"><i class="fab fa-linkedin-in"></i></a>
</div>
<div class="col-lg-4 text-lg-end">
<a class="link-dark text-decoration-none me-3" asp-controller="Home" asp-action="Privacy">Privacy Policy</a>
</div>
</div>
</div>
</footer>
<!-- Bootstrap core JS-->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="~/js/scripts.js"></script>
<!-- Language Switcher -->
<script>
function changeLanguage(lang) {
// Create form to submit language change
const form = document.createElement('form');
form.method = 'POST';
form.action = '/Language/SetLanguage';
const langInput = document.createElement('input');
langInput.type = 'hidden';
langInput.name = 'language';
langInput.value = lang;
form.appendChild(langInput);
const returnUrlInput = document.createElement('input');
returnUrlInput.type = 'hidden';
returnUrlInput.name = 'returnUrl';
returnUrlInput.value = window.location.pathname + window.location.search + window.location.hash;
form.appendChild(returnUrlInput);
document.body.appendChild(form);
form.submit();
}
</script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,48 @@
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

View File

@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View File

@ -0,0 +1,3 @@
@using CarneiroTech
@using CarneiroTech.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

3
Views/_ViewStart.cshtml Normal file
View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9
appsettings.json Normal file
View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

52
deploy-to-oci.sh Normal file
View File

@ -0,0 +1,52 @@
#!/bin/bash
#
# Local Deploy Script - CarneiroTech to OCI
# Usage: ./deploy-to-oci.sh
#
set -e
SERVER="ubuntu@129.146.116.218"
APP_DIR="/home/ubuntu/apps/carneirotech"
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "🚀 CarneiroTech - Deploy to OCI"
echo "================================"
echo ""
# Create tarball excluding unnecessary files
echo "📦 Creating deployment package..."
cd "$LOCAL_DIR"
tar --exclude='bin' \
--exclude='obj' \
--exclude='.vs' \
--exclude='*.user' \
--exclude='.git' \
--exclude='deploy-to-oci.sh' \
-czf /tmp/carneirotech-deploy.tar.gz .
echo "✅ Package created: $(du -h /tmp/carneirotech-deploy.tar.gz | cut -f1)"
echo ""
# Transfer to server
echo "📤 Transferring to server..."
scp /tmp/carneirotech-deploy.tar.gz "$SERVER:$APP_DIR/"
rm /tmp/carneirotech-deploy.tar.gz
# Extract and deploy on server
echo "📥 Extracting on server..."
ssh "$SERVER" << 'ENDSSH'
cd /home/ubuntu/apps/carneirotech
tar -xzf carneirotech-deploy.tar.gz
rm carneirotech-deploy.tar.gz
echo "✅ Files extracted"
echo ""
# Run deploy script
echo "🚀 Running deployment..."
./deploy.sh
ENDSSH
echo ""
echo "✨ Deployment completed!"
echo "🌐 Check your site at: https://carneirotech.com"

26
docker-compose.yml Normal file
View File

@ -0,0 +1,26 @@
version: '3.8'
services:
carneirotech-web:
build:
context: .
dockerfile: Dockerfile
container_name: carneirotech-web
restart: unless-stopped
ports:
- "127.0.0.1:5008:5008"
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:5008
networks:
- carneirotech-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5008/"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
networks:
carneirotech-network:
driver: bridge

BIN
wwwroot/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 579.74 579.74"><defs><style>.cls-1{fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:6px;}</style></defs><line class="cls-1" x1="2.12" y1="2.12" x2="577.62" y2="577.62"/><line class="cls-1" x1="2.12" y1="577.62" x2="577.62" y2="2.12"/></svg>

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2030.6 546.3" style="enable-background:new 0 0 2030.6 546.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#CED4DA;}
</style>
<g>
<path class="st0" d="M1045.4,269.1c-16.8,0-28.9,5.5-41.1,11.1v126.7c11.7,1.1,18.5,1.1,29.6,1.1c40.2,0,45.8-18.4,45.8-44.2v-60.5
C1079.7,284.3,1073.4,269.1,1045.4,269.1L1045.4,269.1z M778.1,262.2c-27.9,0-34.3,15.3-34.3,34.3v10.7h68.6v-10.7
C812.4,277.5,806,262.2,778.1,262.2z M260.4,394.1c0,15,7.1,22.8,22.7,22.8c16.8,0,26.7-5.5,39-11.1v-30.1h-36.7
C268,375.7,260.4,379,260.4,394.1L260.4,394.1z M1305.4,269.1c-28,0-37.7,15.3-37.7,34.3v69.4c0,19.1,9.7,34.4,37.7,34.4
c27.9,0,37.7-15.3,37.7-34.4v-69.4C1343,284.3,1333.3,269.1,1305.4,269.1z M123.3,471.8H41.1v-199H0v-68.6h41.1v-41.2
c0-55.9,23.2-89.2,89.1-89.2H185v68.6h-34.3c-25.7,0-27.3,9.6-27.3,27.5l-0.1,34.3h62.1l-7.3,68.6h-54.9L123.3,471.8L123.3,471.8z
M404.3,472.4h-68.5l-3-17.3c-31.3,17.3-59.2,20.1-77.6,20.1c-50.3,0-77-33.6-77-80c0-54.8,31.2-74.3,87-74.3H322V309
c0-28-3.2-36.2-46.2-36.2h-70.3l6.9-68.6h76.8c94.3,0,115,29.8,115,105.3L404.3,472.4L404.3,472.4z M637.3,277.9
c-42.6-7.3-54.9-8.9-75.4-8.9c-36.9,0-48,8.1-48,39.4v59.2c0,31.3,11.1,39.5,48,39.5c20.5,0,32.8-1.6,75.4-9V465
c-37.3,8.4-61.7,10.6-82.2,10.6c-88.3,0-123.4-46.4-123.4-113.5v-48c0-67.1,35.1-113.6,123.4-113.6c20.6,0,44.9,2.2,82.2,10.6
V277.9L637.3,277.9z M894.6,362H743.8v5.5c0,31.3,11.1,39.5,48,39.5c33.1,0,53.3-1.6,95.9-9V465c-41.1,8.4-62.4,10.6-102.7,10.6
c-88.3,0-123.4-46.4-123.4-113.5v-54.9c0-58.7,26-106.7,116.5-106.7s116.5,47.5,116.5,106.7V362z M1161.9,363.3
c0,64.8-18.5,112.1-130.7,112.1c-40.5,0-64.3-3.6-109-10.4V94.5l82.2-13.7v129.6c17.8-6.6,40.8-10,61.7-10
c82.2,0,95.9,36.9,95.9,96.1L1161.9,363.3L1161.9,363.3z M1425.3,364.7c0,55.9-23.1,110.1-119.7,110.1
c-96.6,0-120.1-54.2-120.1-110.1v-54c0-55.9,23.5-110.2,120.1-110.2c96.6,0,119.7,54.2,119.7,110.2V364.7L1425.3,364.7z
M1688.6,364.7c0,55.9-23.1,110.1-119.7,110.1c-96.6,0-120.1-54.2-120.1-110.1v-54c0-55.9,23.5-110.2,120.1-110.2
c96.6,0,119.7,54.2,119.7,110.2V364.7L1688.6,364.7z M1958.8,471.8h-89.1l-75.3-125.8v125.8h-82.2V94.5l82.2-13.7v242.9l75.3-119.4
h89.1l-82.3,130.3L1958.8,471.8z M1568.7,269.1c-27.9,0-37.6,15.3-37.6,34.3v69.4c0,19.1,9.7,34.4,37.6,34.4
c27.9,0,37.8-15.3,37.8-34.4v-69.4C1606.4,284.3,1596.6,269.1,1568.7,269.1L1568.7,269.1z M2005.7,424.9
c13.8,0,24.9,11.3,24.9,25.4c0,14.3-11,25.5-25,25.5c-13.9,0-25.1-11.2-25.1-25.5c0-14.1,11.3-25.4,25.1-25.4H2005.7z
M2005.6,428.9c-11.2,0-20.3,9.6-20.3,21.4c0,12.1,9.1,21.5,20.4,21.5c11.3,0.1,20.3-9.5,20.3-21.4c0-11.9-9-21.6-20.3-21.6H2005.6
z M2000.9,465.1h-4.5v-28.3c2.4-0.3,4.6-0.7,8-0.7c4.3,0,7.1,0.9,8.8,2.1c1.7,1.3,2.6,3.2,2.6,5.9c0,3.7-2.5,6-5.5,6.9v0.2
c2.5,0.5,4.2,2.7,4.7,6.9c0.7,4.4,1.3,6.1,1.8,7h-4.7c-0.7-0.9-1.4-3.5-1.9-7.2c-0.7-3.6-2.5-5-6.1-5h-3.1L2000.9,465.1
L2000.9,465.1z M2000.9,449.4h3.3c3.7,0,6.9-1.4,6.9-4.9c0-2.5-1.8-5-6.9-5c-1.5,0-2.5,0.1-3.3,0.2V449.4L2000.9,449.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2500 928" style="enable-background:new 0 0 2500 928;" xml:space="preserve">
<style type="text/css">
.st0{fill:#CED4DA;}
</style>
<path class="st0" d="M307.9,112.1h22.2c77.2,1.7,153.1,32.7,207.6,87.7c-20.1,20.6-40.7,40.3-60.4,60.8
c-30.6-27.7-67.5-49.1-107.8-56.6c-59.6-12.6-123.7-1.3-173.7,32.7c-54.5,35.7-91.4,96.1-99.4,160.7c-8.8,63.8,9.2,130.9,50.8,180.4
c39.8,48.2,100.7,78.4,163.6,80.5c58.7,3.4,120-14.7,162.8-55.8c33.6-28.9,49.1-73,54.1-115.8c-69.6,0-139.3,0.4-208.9,0v-86.4H612
c15.1,92.7-6.7,197.1-77.2,263.4c-47,47-112,74.7-178.3,80.1c-64.2,6.3-130.5-5.9-187.5-36.9c-68.4-36.5-122.9-98.2-149.7-170.7
C-5.9,469.5-6.3,394,17.2,326.8c21.4-61.2,62.5-115.4,115.4-153.1C183.3,136.4,245,115.8,307.9,112.1z"/>
<path class="st0" d="M1989.9,133.9h89.8v599c-29.8,0-60,0.4-89.8-0.4C1990.4,533.2,1989.9,333.5,1989.9,133.9L1989.9,133.9z"/>
<path class="st0" d="M811.7,341.5C867,331,927,342.8,972.7,375.9c41.5,29.4,70.5,75.5,79.3,125.8c11.3,58.3-2.9,122.1-40.7,168.2
c-40.7,51.6-107.4,79.3-172.4,75.1c-59.6-3.4-117.4-33.1-152.7-81.8c-39.8-53.7-49.5-127.5-27.7-190.4
C680.4,405.3,742,353.7,811.7,341.5 M824.2,421.2c-22.7,5.9-43.6,18.9-58.7,37.3c-40.7,48.7-38.2,127.9,6.7,173.2
c25.6,26,64.2,38.2,99.8,31c33.1-5.9,62.1-28.1,78-57.5c27.7-49.9,19.7-118.7-22.7-157.7C900.2,422.5,860.3,412,824.2,421.2
L824.2,421.2z"/>
<path class="st0" d="M1256.3,341.5c63.3-12.2,132.6,5.5,179.9,49.9c77.2,69.2,85.6,198.8,19.7,278.5c-39.8,50.3-104.4,78-168.2,75.1
c-60.8-1.7-120.8-31.9-156.9-81.8c-40.7-54.9-49.5-130.5-26.4-194.6C1127.5,403.2,1187.9,353.3,1256.3,341.5 M1268.9,421.2
c-22.7,5.9-43.6,18.9-58.7,36.9c-40.3,47.8-38.6,125.8,4.6,171.6c25.6,27.3,65.4,40.7,102.3,33.1c32.7-6.3,62.1-28.1,78-57.5
c27.3-50.3,19.3-119.1-23.5-158.1C1344.4,422.1,1304.5,412,1268.9,421.2L1268.9,421.2z"/>
<path class="st0" d="M1633.4,365.8c48.2-30.2,112.4-38.6,164.4-12.6c16.4,7.1,29.8,19.3,42.8,31.5c0.4-11.3,0-23.1,0.4-34.8
c28.1,0.4,56.2,0,84.7,0.4v370c-0.4,55.8-14.7,114.9-54.9,155.6c-44,44.9-111.6,58.7-172.4,49.5c-65-9.6-121.6-57-146.8-117
c25.2-12.2,51.6-21.8,77.6-33.1c14.7,34.4,44.5,63.8,81.8,70.5c37.3,6.7,80.5-2.5,104.9-33.6c26-31.9,26-75.5,24.7-114.5
c-19.3,18.9-41.5,35.7-68.4,41.9c-58.3,16.4-122.5-3.8-167.4-43.2c-45.3-39.4-72.1-100.3-69.6-160.7
C1536.5,467.4,1575.1,401.5,1633.4,365.8 M1720.2,419.5c-25.6,4.2-49.5,18.5-65.9,38.2c-39.4,47-39.4,122.1,0.4,168.2
c22.7,27.3,59.1,42.4,94.4,38.6c33.1-3.4,63.8-24.3,80.1-53.3c27.7-49.1,23.1-115.8-14.3-158.6
C1791.9,426.2,1755,413.2,1720.2,419.5L1720.2,419.5z"/>
<path class="st0" d="M2187.5,387.2c50.3-47,127.9-62.9,192.5-38.2c61.2,23.1,100.3,81.4,120,141.4c-91,37.8-181.6,75.1-272.7,112.8
c12.6,23.9,31.9,45.7,57.9,54.5c36.5,13,80.1,8.4,110.7-15.9c12.2-9.2,21.8-21.4,31-33.1c23.1,15.5,46.1,30.6,69.2,46.1
c-32.7,49.1-87.7,83.5-146.8,88.9c-65.4,8-135.1-17.2-177.4-68.4C2102.3,594.9,2109.1,459.8,2187.5,387.2 M2232.4,464.8
c-14.3,20.6-20.1,45.7-19.7,70.5c60.8-25.2,121.6-50.3,182.5-75.9c-10.1-23.5-34.4-37.8-59.1-41.5
C2296.1,410.7,2254.6,432.1,2232.4,464.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2500 1000" style="enable-background:new 0 0 2500 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#CED4DA;}
</style>
<path class="st0" d="M0,0v68.4h486.6V0H0z M555.6,0v68.4H1249c0,0-70.8-68.4-164.6-68.4H555.6z M1385.1,0v68.4h419.5L1779.7,0
H1385.1z M2105.4,0l-24.9,68.4h415.7V0H2105.4z M0,133.1v68.4h486.6v-68.4H0z M555.6,133.2v68.3h773.9c0,0-9-52.7-24.8-68.3
L555.6,133.2L555.6,133.2z M1385.1,133.2v68.3h465.5l-23-68.3L1385.1,133.2L1385.1,133.2z M2055.6,133.2l-23,68.3h463.7v-68.3
H2055.6z M139.8,266.1v68.5h210.7v-68.5L139.8,266.1L139.8,266.1z M695.4,266.1v68.5h210.7v-68.5L695.4,266.1L695.4,266.1z
M1111.1,266.1v68.5h210.7c0,0,13.4-36.2,13.4-68.5L1111.1,266.1L1111.1,266.1z M1524.9,266.1v68.5h373.6l-24.9-68.5L1524.9,266.1
L1524.9,266.1z M2009.7,266.1l-25,68.5h375.5v-68.5L2009.7,266.1L2009.7,266.1z M139.8,399.3v68.4h210.7v-68.4H139.8L139.8,399.3z
M695.4,399.3v68.4h538.3c0,0,45-35.1,59.4-68.4H695.4z M1524.9,399.3v68.4h210.7v-38.1l13.4,38.1h386l14.4-38.1v38.1h210.7v-68.4
h-395.6l-21,57.9l-21.1-57.9H1524.9z M139.8,532.3v68.4h210.7v-68.4H139.8z M695.4,532.3v68.4h597.7c-14.3-33.2-59.4-68.4-59.4-68.4
H695.4z M1524.9,532.3v68.4h210.7v-68.4H1524.9z M1773.9,532.3l25.5,68.4h289.5l24.2-68.4H1773.9z M2149.4,532.3v68.4h210.7v-68.4
H2149.4z M139.8,665.4v68.4h210.7v-68.4H139.8z M695.4,665.4v68.4h210.7v-68.4H695.4z M1111.1,665.4v68.4h224.1
c0-32.3-13.4-68.4-13.4-68.4H1111.1L1111.1,665.4z M1524.9,665.4v68.4h210.7v-68.4H1524.9z M1821.8,665.4l24.7,68.4h194l24.9-68.4
H1821.8z M2149.4,665.4v68.4h210.7v-68.4H2149.4z M3.8,798.4v68.5h486.6v-68.5H3.8z M555.6,798.4v68.5h749.1
c15.8-15.7,24.8-68.5,24.8-68.5H555.6L555.6,798.4z M1388.9,798.4v68.5h346.8v-68.5H1388.9z M1869.7,798.4l25.4,68.5h98.7l23.8-68.5
H1869.7z M2149.4,798.4v68.5H2500v-68.5H2149.4z M3.8,931.6v68.4h486.6v-68.4H3.8z M555.6,931.6v68.3h528.8
c93.8,0,164.6-68.3,164.6-68.3H555.6z M1388.9,931.6v68.4h346.8v-68.4H1388.9z M1917.9,931.6l24.4,68.2l4.2,0.1l24.8-68.3H1917.9z
M2149.4,931.6v68.4H2500v-68.4H2149.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2500 534" style="enable-background:new 0 0 2500 534;" xml:space="preserve">
<style type="text/css">
.st0{fill:#CED4DA;}
</style>
<path class="st0" d="M2500,241.6v-44h-54.6v-68.4l-1.8,0.6l-51.3,15.7l-1,0.3v51.8h-80.9v-28.9c0-13.4,3-23.7,8.9-30.6
c5.9-6.8,14.3-10.2,25-10.2c7.7,0,15.7,1.8,23.7,5.4l2,0.9V88l-0.9-0.3c-7.5-2.7-17.7-4.1-30.3-4.1c-15.9,0-30.4,3.5-43,10.3
c-12.6,6.9-22.6,16.7-29.5,29.2c-6.9,12.5-10.5,26.9-10.5,42.8v31.7h-38v44h38v185.2h54.6V241.6h80.9v117.7c0,48.5,22.9,73,68,73
c7.4,0,15.2-0.9,23.2-2.6c8.1-1.7,13.6-3.5,16.9-5.4l0.7-0.4v-44.4l-2.2,1.5c-3,2-6.7,3.6-11,4.8c-4.3,1.2-8,1.8-10.8,1.8
c-10.6,0-18.4-2.8-23.2-8.5c-4.9-5.7-7.4-15.6-7.4-29.4V241.6H2500L2500,241.6z M2095.9,387.7c-19.8,0-35.4-6.6-46.4-19.5
c-11.1-13-16.7-31.5-16.7-55.1c0-24.3,5.6-43.3,16.7-56.6c11-13.1,26.5-19.8,46-19.8c18.9,0,34,6.4,44.8,19
c10.8,12.6,16.3,31.5,16.3,56.1c0,24.9-5.2,44-15.4,56.8C2131,381.3,2115.8,387.7,2095.9,387.7 M2098.3,192.1
c-37.8,0-67.8,11.1-89.2,32.9c-21.4,21.8-32.2,52.1-32.2,89.9c0,35.9,10.6,64.7,31.5,85.8c20.9,21,49.3,31.7,84.5,31.7
c36.6,0,66.1-11.2,87.4-33.4c21.4-22.1,32.2-52.1,32.2-89c0-36.4-10.2-65.5-30.2-86.4C2162.1,202.7,2133.9,192.1,2098.3,192.1
M1888.8,192.1c-25.7,0-47,6.6-63.2,19.5c-16.3,13-24.6,30.1-24.6,50.8c0,10.8,1.8,20.3,5.3,28.4c3.5,8.1,9,15.3,16.3,21.3
c7.2,6,18.4,12.2,33.2,18.6c12.4,5.1,21.7,9.4,27.6,12.9c5.8,3.3,9.8,6.7,12.1,10c2.2,3.2,3.4,7.6,3.4,13
c0,15.4-11.5,22.9-35.3,22.9c-8.8,0-18.8-1.8-29.8-5.5c-10.9-3.6-21.2-8.8-30.6-15.5l-2.3-1.6v52.5l0.8,0.4
c7.7,3.6,17.5,6.6,28.9,8.9c11.5,2.4,21.9,3.6,30.9,3.6c27.9,0,50.4-6.6,66.8-19.6c16.5-13.1,24.9-30.6,24.9-52.1
c0-15.4-4.5-28.7-13.4-39.4c-8.8-10.6-24.1-20.3-45.4-28.9c-17-6.8-27.9-12.5-32.4-16.8c-4.3-4.2-6.5-10.1-6.5-17.7
c0-6.7,2.7-12,8.3-16.3c5.6-4.3,13.4-6.6,23.2-6.6c9.1,0,18.4,1.4,27.6,4.3c9.2,2.8,17.4,6.6,24.1,11.2l2.2,1.5v-49.8l-0.9-0.4
c-6.3-2.7-14.5-5-24.5-6.8C1905.8,193,1896.7,192.1,1888.8,192.1 M1658.7,387.7c-19.8,0-35.4-6.6-46.4-19.5
c-11.1-13-16.7-31.5-16.7-55.1c0-24.3,5.6-43.3,16.7-56.6c11-13.1,26.5-19.8,46-19.8c18.9,0,34,6.4,44.8,19
c10.8,12.6,16.3,31.5,16.3,56.1c0,24.9-5.2,44-15.4,56.8C1693.9,381.3,1678.7,387.7,1658.7,387.7 M1661.2,192.1
c-37.8,0-67.8,11.1-89.2,32.9c-21.4,21.8-32.2,52.1-32.2,89.9c0,35.9,10.6,64.7,31.5,85.8c20.9,21,49.3,31.7,84.5,31.7
c36.6,0,66.1-11.2,87.4-33.4c21.4-22.1,32.2-52.1,32.2-89c0-36.4-10.2-65.5-30.2-86.4C1725,202.7,1696.7,192.1,1661.2,192.1
M1456.9,237.3v-39.7h-53.9v229.2h53.9V309.6c0-19.9,4.5-36.3,13.4-48.7c8.8-12.2,20.5-18.4,34.9-18.4c4.9,0,10.3,0.8,16.2,2.4
c5.8,1.6,10.1,3.3,12.6,5.1l2.3,1.6v-54.4l-0.9-0.4c-5-2.1-12.1-3.2-21.1-3.2c-13.5,0-25.7,4.4-36.1,12.9
c-9.1,7.5-15.7,17.9-20.7,30.7H1456.9z M1306.4,192.1c-24.7,0-46.8,5.3-65.6,15.8c-18.8,10.5-33.3,25.4-43.2,44.5
c-9.9,19-14.9,41.1-14.9,65.9c0,21.7,4.8,41.5,14.4,59c9.6,17.5,23.2,31.3,40.3,40.8c17.2,9.5,37,14.3,58.9,14.3
c25.6,0,47.5-5.1,65-15.2l0.7-0.4v-49.4l-2.3,1.7c-7.9,5.8-16.8,10.4-26.4,13.7c-9.5,3.3-18.2,5-25.8,5c-21.2,0-38.1-6.6-50.5-19.7
c-12.4-13.1-18.6-31.4-18.6-54.5c0-23.2,6.5-42.1,19.4-55.9c12.8-13.8,29.8-20.9,50.6-20.9c17.7,0,35,6,51.3,17.9l2.3,1.6v-52
l-0.7-0.4c-6.1-3.4-14.5-6.3-24.9-8.4C1326.2,193.2,1316,192.1,1306.4,192.1 M1145.6,197.6h-53.9v229.2h53.9V197.6L1145.6,197.6z
M1119.2,100c-8.9,0-16.6,3-23,9c-6.4,6-9.6,13.6-9.6,22.5c0,8.8,3.2,16.2,9.5,22c6.3,5.8,14,8.8,23.1,8.8c9,0,16.8-3,23.2-8.8
c6.4-5.9,9.6-13.3,9.6-22.1c0-8.6-3.2-16.1-9.4-22.2C1136.4,103.1,1128.6,100,1119.2,100 M984.7,180.7v246.1h55V107h-76.1
l-96.8,237.5L772.9,107h-79.2v319.8h51.7V180.7h1.8l99.2,246.1h39l97.6-246.1L984.7,180.7L984.7,180.7z"/>
<path class="st0" d="M253.6,253.7H0V0.1h253.6V253.7z"/>
<path class="st0" d="M533.6,253.7H280V0.1h253.6L533.6,253.7L533.6,253.7z"/>
<path class="st0" d="M253.6,533.9H0V280.3h253.6V533.9z"/>
<path class="st0" d="M533.6,533.9H280V280.3h253.6L533.6,533.9L533.6,533.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

368
wwwroot/css/custom.css Normal file
View File

@ -0,0 +1,368 @@
/* Custom styles for Carneiro Tech - RED THEME */
/* Color Palette inspired by logo:
Primary Red: #C42127 (vibrant red from logo)
Dark Red: #8B1E23 (darker shade)
Light Background: #FAF7F5 (warm beige/cream)
Accent: #E63946 (bright red for hover)
*/
:root {
--primary-red: #C42127;
--dark-red: #8B1E23;
--accent-red: #E63946;
--light-bg: #FAF7F5;
--cream: #FFF8F0;
}
/* Portfolio hover effect with overlay - IMPROVED */
.portfolio-item {
position: relative;
overflow: hidden;
transition: transform 0.3s ease;
}
.portfolio-item:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(196, 33, 39, 0.3);
}
.portfolio-hover {
position: absolute;
width: 100%;
height: 100%;
background: rgba(196, 33, 39, 0.95); /* Red overlay instead of yellow */
opacity: 0;
transition: opacity 0.3s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
top: 0;
left: 0;
}
.portfolio-item:hover .portfolio-hover {
opacity: 1;
}
.portfolio-hover-content {
text-align: center;
padding: 2rem;
}
.portfolio-hover-content p {
color: #ffffff !important;
font-size: 1rem;
margin: 0;
font-weight: 600;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
/* Gradient backgrounds for cases without images - RED TONES */
.case-gradient-1 {
background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);
}
.case-gradient-2 {
background: linear-gradient(135deg, #E63946 0%, #C42127 100%);
}
.case-gradient-3 {
background: linear-gradient(135deg, #D62828 0%, #9D0208 100%);
}
.case-gradient-4 {
background: linear-gradient(135deg, #DC2F02 0%, #9D0208 100%);
}
.case-gradient-5 {
background: linear-gradient(135deg, #F48C06 0%, #E85D04 100%);
}
.case-gradient-6 {
background: linear-gradient(135deg, #8B1E23 0%, #5C0F13 100%);
}
/* Timeline icon styling - RED */
.timeline-image {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--primary-red);
}
.timeline-image i {
color: #ffffff;
}
/* Contact section background - RED GRADIENT */
#contact {
background: linear-gradient(135deg, #C42127 0%, #8B1E23 100%);
color: white;
}
/* Enhanced button styles - RED */
.btn-primary {
background-color: var(--primary-red);
border-color: var(--primary-red);
color: #ffffff;
font-weight: 700;
transition: all 0.3s ease;
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
background-color: var(--dark-red);
border-color: var(--dark-red);
color: #ffffff;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(196, 33, 39, 0.3);
}
.btn-outline-primary {
border-color: var(--primary-red);
color: var(--primary-red);
transition: all 0.3s ease;
}
.btn-outline-primary:hover,
.btn-outline-primary:focus,
.btn-outline-primary:active {
background-color: var(--primary-red);
border-color: var(--primary-red);
color: #ffffff;
}
/* Badge styling - RED */
.badge.bg-primary {
background-color: var(--primary-red) !important;
color: #ffffff;
}
.badge.bg-secondary {
background-color: #6c757d !important;
}
/* Case detail page enhancements - RED */
.case-content a {
color: var(--primary-red);
text-decoration: underline;
}
.case-content a:hover {
color: var(--dark-red);
}
/* Masthead - Natural background with RED TEXT */
.masthead {
position: relative;
background-size: cover;
background-position: center;
}
/* Subtle dark overlay for better text contrast - LIGHTER */
.masthead::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.25); /* Apenas 25% de escurecimento */
z-index: 1;
}
.masthead .container {
position: relative;
z-index: 2;
}
/* LIGHT NEUTRAL TEXT that complements red - professional look */
.masthead-subheading {
color: #FFE4B5 !important; /* Bege claro dourado - warm neutral */
text-shadow:
3px 3px 8px rgba(0, 0, 0, 0.9),
-1px -1px 3px rgba(0, 0, 0, 0.7),
1px 1px 2px rgba(0, 0, 0, 0.5); /* Sombra escura para contraste */
font-weight: 700;
letter-spacing: 1px;
}
.masthead-heading {
color: #FFF8DC !important; /* Cornsilk - creme claro quente */
text-shadow:
4px 4px 10px rgba(0, 0, 0, 0.95),
-2px -2px 4px rgba(0, 0, 0, 0.8),
2px 2px 3px rgba(0, 0, 0, 0.6); /* Sombra escura forte */
font-weight: 900;
letter-spacing: 2px;
}
/* Services icon colors - RED */
.fa-stack .text-primary {
color: var(--primary-red) !important;
}
/* Navbar styling - LIGHT CREAM BACKGROUND */
#mainNav {
background-color: var(--light-bg) !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
#mainNav .navbar-nav .nav-link {
color: #2c3e50 !important;
font-weight: 600;
transition: color 0.3s ease;
}
#mainNav .navbar-nav .nav-link:hover,
#mainNav .navbar-nav .nav-link:focus {
color: var(--primary-red) !important;
}
#mainNav.navbar-shrink {
background-color: var(--cream) !important;
}
/* Navbar toggler - RED */
.navbar-toggler {
border-color: var(--primary-red);
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(196, 33, 39, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
/* Navbar brand hover */
.navbar-brand:hover {
opacity: 0.8;
}
/* Form control focus - RED */
.form-control:focus {
border-color: var(--primary-red);
box-shadow: 0 0 0 0.2rem rgba(196, 33, 39, 0.25);
}
/* Loading animation for images */
img {
transition: opacity 0.3s ease-in-out;
}
/* Tag buttons - RED THEME */
.btn-group-wrap .btn {
margin: 0.25rem;
transition: all 0.3s ease;
}
.btn-group-wrap .btn-primary {
background-color: var(--primary-red);
border-color: var(--primary-red);
}
.btn-group-wrap .btn-outline-primary {
color: var(--primary-red);
border-color: var(--primary-red);
}
.btn-group-wrap .btn-outline-primary:hover {
background-color: var(--primary-red);
color: white;
}
/* Portfolio caption styling */
.portfolio-caption {
padding: 1.5rem;
background-color: #ffffff;
}
.portfolio-caption-heading {
font-weight: 700;
color: #212529;
margin-bottom: 0.5rem;
}
.portfolio-caption-subheading {
color: #6c757d;
}
/* Section headings - RED accents */
.section-heading {
color: #212529;
position: relative;
padding-bottom: 1rem;
}
.section-heading::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 3px;
background-color: var(--primary-red);
}
/* Footer styling */
.footer {
background-color: #212529;
color: #ffffff;
}
.footer a {
color: #ffffff;
transition: color 0.3s ease;
}
.footer a:hover {
color: var(--accent-red);
}
/* Button social - RED */
.btn-social {
background-color: var(--primary-red);
border-color: var(--primary-red);
transition: all 0.3s ease;
}
.btn-social:hover {
background-color: var(--dark-red);
border-color: var(--dark-red);
transform: translateY(-3px);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.portfolio-hover-content p {
font-size: 0.9rem;
padding: 1rem;
}
.timeline-image i {
font-size: 2rem !important;
}
#mainNav {
background-color: var(--cream) !important;
}
}
/* Smooth scroll behavior */
html {
scroll-behavior: smooth;
}
/* Portfolio item image container */
.portfolio-item img,
.portfolio-item > a > div[style*="height"] {
width: 100%;
transition: transform 0.3s ease;
}
.portfolio-item:hover img,
.portfolio-item:hover > a > div[style*="height"] {
transform: scale(1.05);
}

22
wwwroot/css/site.css Normal file
View File

@ -0,0 +1,22 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}

11316
wwwroot/css/styles.css Normal file

File diff suppressed because it is too large Load Diff

BIN
wwwroot/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
wwwroot/img/LogoPequeno.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

View File

@ -0,0 +1,26 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
<!-- Carneiro Tech Logo - Optimized with Transparent Background -->
<!-- Red spiral/shell design -->
<defs>
<linearGradient id="redGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#C42127;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B1E23;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Spiral shell design inspired by Carneiro logo -->
<path d="M100,20 Q150,30 160,80 Q165,130 130,160 Q90,180 60,150 Q40,110 60,70 Q80,40 100,20 Z"
fill="url(#redGradient)"
stroke="#8B1E23"
stroke-width="2"/>
<path d="M100,40 Q130,45 135,75 Q138,105 115,125 Q90,140 70,120 Q55,95 70,75 Q85,55 100,40 Z"
fill="#C42127"
opacity="0.8"/>
<path d="M100,60 Q115,63 118,80 Q120,97 108,108 Q95,118 83,107 Q73,93 83,80 Q93,68 100,60 Z"
fill="#E63946"/>
<!-- Center dot -->
<circle cx="100" cy="85" r="8" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 1008 B

67
wwwroot/img/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 744 KiB

54
wwwroot/js/scripts.js Normal file
View File

@ -0,0 +1,54 @@
/*!
* Start Bootstrap - Agency v7.0.12 (https://startbootstrap.com/theme/agency)
* Copyright 2013-2023 Start Bootstrap
* Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-agency/blob/master/LICENSE)
*/
//
// Scripts
//
window.addEventListener('DOMContentLoaded', event => {
// Navbar shrink function
var navbarShrink = function () {
const navbarCollapsible = document.body.querySelector('#mainNav');
if (!navbarCollapsible) {
return;
}
if (window.scrollY === 0) {
navbarCollapsible.classList.remove('navbar-shrink')
} else {
navbarCollapsible.classList.add('navbar-shrink')
}
};
// Shrink the navbar
navbarShrink();
// Shrink the navbar when page is scrolled
document.addEventListener('scroll', navbarShrink);
// Activate Bootstrap scrollspy on the main nav element
const mainNav = document.body.querySelector('#mainNav');
if (mainNav) {
new bootstrap.ScrollSpy(document.body, {
target: '#mainNav',
rootMargin: '0px 0px -40%',
});
};
// Collapse responsive navbar when toggler is visible
const navbarToggler = document.body.querySelector('.navbar-toggler');
const responsiveNavItems = [].slice.call(
document.querySelectorAll('#navbarResponsive .nav-link')
);
responsiveNavItems.map(function (responsiveNavItem) {
responsiveNavItem.addEventListener('click', () => {
if (window.getComputedStyle(navbarToggler).display !== 'none') {
navbarToggler.click();
}
});
});
});

4
wwwroot/js/site.js Normal file
View File

@ -0,0 +1,4 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,427 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More