@gp_jcisneros/errors
Version:
Error handling utilities for GreenPay microservices and validation middleware
974 lines (753 loc) • 27.7 kB
Markdown
# @gp_jcisneros/errors
Biblioteca de manejo de errores estandarizada para microservicios GreenPay con campos requeridos para transformación empresarial y **middleware de validación Zod**.
## 📦 Instalación
**Requisitos:** Node.js 22.x o superior
```bash
npm install @gp_jcisneros/errors
```
## 🚀 Uso Rápido
```javascript
const {
CustomError,
HttpError,
ValidationError,
DatabaseError,
AWSError,
IntegrationError,
ZodError,
ResponseHandler,
// Zod Error Handlers
convertZodErrorToValidationError,
createDynamicSchemaValidation,
createBaseSchemaValidation,
} = require('@gp_jcisneros/errors');
// Crear un error personalizado con el nuevo constructor
const error = new CustomError('Algo salió mal', {
errorCode: 'GP_VALIDATION_ERROR',
description: 'Error de validación en los datos de entrada',
integration: 'transaction-service',
statusCode: 400
});
// Usar métodos estáticos para crear errores
const staticError = CustomError.create(
'Error de ejemplo',
'GP_STATIC_ERROR',
'Descripción del error estático',
'static-service',
400
);
const minimalError = CustomError.createMinimal(
'Error mínimo',
'GP_MINIMAL_ERROR',
'minimal-service'
);
// Verificar campos requeridos
console.log('Tiene campos requeridos:', error.hasRequiredFields());
console.log('Campos requeridos:', error.getRequiredFields());
// Convertir a JSON
const jsonFormat = error.toJSON();
```
## 🏗️ Arquitectura de Errores
Este NPM está diseñado para estandarizar el manejo de errores en todos los módulos de GreenPay con **campos requeridos** para facilitar la transformación empresarial.
### **Campos Requeridos Estandarizados:**
Todos los errores incluyen tres campos requeridos para estandarización:
- **`errorCode`**: Código alfanumérico que identifica el error
- **`description`**: Descripción detallada del error
- **`integration`**: Nombre del módulo de integración donde se está usando el error
### **Nuevo Constructor Flexible:**
```javascript
// Nuevo constructor con objeto de opciones
const error = new CustomError('Error de validación', {
errorCode: 'GP_VALIDATION_ERROR',
description: 'Error de validación en los datos de entrada',
integration: 'transaction-service',
statusCode: 400,
// Campos adicionales personalizados
customField: 'valor personalizado',
metadata: { key: 'value' }
});
// Métodos estáticos para crear errores
const staticError = CustomError.create(
'Error de ejemplo',
'GP_STATIC_ERROR',
'Descripción del error estático',
'static-service',
400
);
const minimalError = CustomError.createMinimal(
'Error mínimo',
'GP_MINIMAL_ERROR',
'minimal-service'
);
```
## 📚 Clases de Error
### CustomError
Clase base con **campos requeridos estandarizados** y constructor flexible.
#### Constructor Nuevo
```javascript
const { CustomError } = require('@gp_jcisneros/errors');
// Constructor básico
const error = new CustomError('Mensaje de error');
// Constructor con opciones
const errorWithOptions = new CustomError('Error de validación', {
errorCode: 'GP_VALIDATION_ERROR',
description: 'Error de validación en los datos de entrada',
integration: 'transaction-service',
statusCode: 400
});
// Métodos estáticos
const staticError = CustomError.create(
'Error de ejemplo',
'GP_STATIC_ERROR',
'Descripción del error estático',
'static-service',
400
);
const minimalError = CustomError.createMinimal(
'Error mínimo',
'GP_MINIMAL_ERROR',
'minimal-service'
);
// Usando métodos setter
const errorWithSetters = new CustomError('Error de base de datos');
errorWithSetters
.setErrorCode('GP_DB_CONNECTION_ERROR')
.setDescription('Error de conexión a la base de datos')
.setIntegration('database-service');
// Usando setRequiredFields para establecer todos a la vez
const errorWithSetRequired = new CustomError('Error de autenticación');
errorWithSetRequired.setRequiredFields(
'GP_AUTH_ERROR',
'Error de autenticación del usuario',
'auth-service'
);
// Validar campos requeridos
console.log('Tiene campos requeridos:', errorWithOptions.hasRequiredFields());
console.log('Campos requeridos:', errorWithOptions.getRequiredFields());
// Convertir a JSON
const jsonFormat = errorWithOptions.toJSON();
// {
// name: 'CustomError',
// message: 'Error de validación',
// statusCode: 400,
// timestamp: '2024-12-19T10:30:00.000Z',
// stack: '...',
// errorCode: 'GP_VALIDATION_ERROR',
// description: 'Error de validación en los datos de entrada',
// integration: 'transaction-service'
// }
```
### HttpError
Errores HTTP predefinidos con campos requeridos automáticos.
```javascript
const { HttpError } = require('@gp_jcisneros/errors');
// Errores comunes con campos requeridos automáticos
const badRequest = HttpError.badRequest('Datos inválidos');
// errorCode: 'HTTP_400', integration: 'http-service', statusCode: 400
const unauthorized = HttpError.unauthorized('Token inválido');
// errorCode: 'HTTP_401', integration: 'http-service', statusCode: 401
const notFound = HttpError.notFound('Usuario no encontrado');
// errorCode: 'HTTP_404', integration: 'http-service', statusCode: 404
const conflict = HttpError.conflict('Usuario ya existe');
// errorCode: 'HTTP_409', integration: 'http-service', statusCode: 409
const internalError = HttpError.internalServerError('Error interno');
// errorCode: 'HTTP_500', integration: 'http-service', statusCode: 500
// Constructor personalizado
const customHttpError = new HttpError('Error personalizado', 418);
// errorCode: 'HTTP_418', integration: 'http-service', statusCode: 418
// Verificar campos requeridos
console.log('Tiene campos requeridos:', badRequest.hasRequiredFields());
```
### ValidationError
Errores específicos para validación de datos con campos requeridos automáticos.
```javascript
const { ValidationError } = require('@gp_jcisneros/errors');
// Errores de validación con campos requeridos automáticos
const requiredError = ValidationError.required('email');
// errorCode: 'VALIDATION_EMAIL', integration: 'validation-service', statusCode: 400
const emailError = ValidationError.invalidEmail('email', 'invalid@');
// errorCode: 'VALIDATION_EMAIL', integration: 'validation-service', statusCode: 400
const lengthError = ValidationError.minLength('password', 8, '123');
// errorCode: 'VALIDATION_PASSWORD', integration: 'validation-service', statusCode: 400
const formatError = ValidationError.invalidFormat('phone', 'XXX-XXX-XXXX', '123');
// errorCode: 'VALIDATION_PHONE', integration: 'validation-service', statusCode: 400
// Error personalizado para un campo
const customError = ValidationError.forField('age', 'Debe ser mayor de 18', 15);
// errorCode: 'VALIDATION_AGE', integration: 'validation-service', statusCode: 400
// Constructor directo
const directError = new ValidationError('Error personalizado', 'field', 'value');
// errorCode: 'VALIDATION_FIELD', integration: 'validation-service', statusCode: 400
// Obtener detalles de validación
const details = requiredError.getValidationDetails();
```
### DatabaseError
Errores específicos para operaciones de base de datos con campos requeridos automáticos.
```javascript
const { DatabaseError } = require('@gp_jcisneros/errors');
// Errores de base de datos con campos requeridos automáticos
const connectionError = DatabaseError.connection('Conexión fallida');
// errorCode: 'DB_CONNECT', integration: 'database-service', statusCode: 500
const notFoundError = DatabaseError.notFound('users');
// errorCode: 'DB_GET', integration: 'database-service', statusCode: 500
const duplicateError = DatabaseError.duplicateKey('users', 'email');
// errorCode: 'DB_INSERT', integration: 'database-service', statusCode: 500
const insertError = DatabaseError.insert('Error al insertar', 'users');
// errorCode: 'DB_INSERT', integration: 'database-service', statusCode: 500
const updateError = DatabaseError.update('Error al actualizar', 'users');
// errorCode: 'DB_UPDATE', integration: 'database-service', statusCode: 500
const deleteError = DatabaseError.delete('Error al eliminar', 'users');
// errorCode: 'DB_DELETE', integration: 'database-service', statusCode: 500
const constraintError = DatabaseError.constraintViolation('Constraint failed', 'users');
// errorCode: 'DB_CONSTRAINT', integration: 'database-service', statusCode: 500
// Constructor directo
const directDbError = new DatabaseError('Error personalizado', 'QUERY', 'table');
// errorCode: 'DB_QUERY', integration: 'database-service', statusCode: 500
// Obtener detalles de base de datos
const details = connectionError.getDatabaseDetails();
```
### AWSError
Errores específicos para servicios AWS con campos requeridos automáticos.
```javascript
const { AWSError } = require('@gp_jcisneros/errors');
// Errores de AWS con campos requeridos automáticos
const dynamoError = AWSError.dynamoDB('ConditionalCheckFailedException', 'Condition check failed');
// errorCode: 'AWS_DYNAMODB', integration: 'aws-service', statusCode: 500
const s3Error = AWSError.s3('NoSuchKey', 'Object not found');
// errorCode: 'AWS_S3', integration: 'aws-service', statusCode: 500
const lambdaError = AWSError.lambda('ResourceNotFoundException', 'Function not found');
// errorCode: 'AWS_LAMBDA', integration: 'aws-service', statusCode: 500
const sqsError = AWSError.sqs('QueueDoesNotExist', 'Queue not found');
// errorCode: 'AWS_SQS', integration: 'aws-service', statusCode: 500
// Error para servicio personalizado
const ec2Error = AWSError.forService('EC2', 'InvalidInstanceID.NotFound', 'Instance not found');
// errorCode: 'AWS_EC2', integration: 'aws-service', statusCode: 500
// Constructor directo
const directAwsError = new AWSError('Error personalizado', 'SERVICE', 'ERROR_CODE');
// errorCode: 'AWS_SERVICE', integration: 'aws-service', statusCode: 500
// Obtener detalles de AWS
const details = dynamoError.getAWSDetails();
```
### IntegrationError
Errores específicos para integraciones externas con campos requeridos automáticos.
```javascript
const { IntegrationError } = require('@gp_jcisneros/errors');
// Errores de integraciones específicas con campos requeridos automáticos
const cybersourceError = IntegrationError.cybersource('Card stolen', 'CYBERSOURCE_05');
// errorCode: 'CYBERSOURCE_05', integration: 'cybersource', statusCode: 502
const stripeError = IntegrationError.stripe('Card declined', 'STRIPE_card_declined');
// errorCode: 'STRIPE_card_declined', integration: 'stripe', statusCode: 502
const adyenError = IntegrationError.adyen('Payment failed', 'ADYEN_123');
// errorCode: 'ADYEN_123', integration: 'adyen', statusCode: 502
// Error personalizado para integración
const customIntegrationError = IntegrationError.custom('Custom error', 'CUSTOM_001', 'custom-service');
// errorCode: 'CUSTOM_001', integration: 'custom-service', statusCode: 502
// Constructor directo con contexto
const directIntegrationError = new IntegrationError('Error personalizado', 'CODE', 'service', { context: 'data' });
// errorCode: 'CODE', integration: 'service', statusCode: 502, context: { context: 'data' }
// Obtener detalles de integración
const details = cybersourceError.getIntegrationDetails();
```
### ZodError
Errores específicos para validación de Zod con campos requeridos automáticos y conversión estandarizada.
```javascript
const { ZodError } = require('@gp_jcisneros/errors');
const { z } = require('zod');
// Crear error desde un ZodError de validación
const schema = z.object({
email: z.string().email('Email inválido'),
name: z.string().min(2, 'Nombre muy corto')
});
try {
schema.parse({ email: 'invalid', name: 'A' });
} catch (zodError) {
const error = ZodError.fromZodError(zodError, 'user-service');
// errorCode: 'ZOD-001', integration: 'user-service', statusCode: 400
}
// Errores específicos de Zod
const requiredError = ZodError.required('email', 'auth-service');
// errorCode: 'ZOD-002', integration: 'auth-service', statusCode: 400
const providerError = ZodError.unsupportedProvider('paypal', 'credit-card', 'payment-service');
// errorCode: 'ZOD-003', integration: 'payment-service', statusCode: 400
const genericError = ZodError.generic(new Error('Internal error'), 'validation-service');
// errorCode: 'ZOD-004', integration: 'validation-service', statusCode: 500
const schemaError = ZodError.schemaValidation('Schema failed', 'user', { invalid: 'data' }, 'user-service');
// errorCode: 'ZOD-005', integration: 'user-service', statusCode: 400
const dynamicError = ZodError.dynamicSchema('Dynamic schema failed', 'stripe', 'payment-service');
// errorCode: 'ZOD-006', integration: 'payment-service', statusCode: 400
// Constructor directo
const directError = new ZodError('Error personalizado', 'field', 'value', null);
// errorCode: 'ZOD_FIELD', integration: 'zod-validation', statusCode: 400
// Obtener detalles de Zod
const details = requiredError.getZodDetails();
// Obtener errores de validación
const validationErrors = error.getValidationErrors();
// Verificar si tiene detalles de Zod
const hasDetails = error.hasZodDetails();
```
### ResponseHandler
Clase para manejar respuestas estandarizadas de API con formato consistente para éxito y error.
```javascript
const { ResponseHandler } = require('@gp_jcisneros/errors');
// Handler básico
const basicHandler = ResponseHandler.createHandler();
// Handler para transacciones
const transactionHandler = ResponseHandler.createTransactionHandler();
// Handler para CRUD
const crudHandler = ResponseHandler.createCRUDHandler();
// Handler personalizado
const customHandler = ResponseHandler.createCustomHandler(
'Operación exitosa',
'Error en la operación'
);
// Uso en Express
app.get('/users/:id', async (req, res) => {
try {
const user = await getUser(req.params.id);
return basicHandler(res, user);
} catch (error) {
return basicHandler(res, null, error);
}
});
// Uso directo de la clase
const handler = new ResponseHandler({
defaultSuccessMessage: 'Operación exitosa',
defaultErrorMessage: 'Error interno',
includeStack: false
});
handler.success(res, { id: 123, name: 'John' });
handler.error(res, new Error('User not found'));
```
### Middleware de Validación Dinámica
```
## 🔧 Zod Error Handler Middleware
Middleware y utilidades para manejar errores de validación de Zod de manera estandarizada.
### Características
- ✅ Conversión automática de errores de Zod a `ValidationError`
- ✅ Middleware reutilizable para validación dinámica
- ✅ Middleware para validación con schema base
- ✅ Formato de respuesta estandarizado
- ✅ Soporte para validaciones personalizadas
- ✅ Integración con diferentes frameworks (Express, Fastify, etc.)
### Middleware de Validación Dinámica
```javascript
const { createDynamicSchemaValidation } = require('@gp_jcisneros/errors');
const { z } = require('zod');
// Función para crear schema dinámico
const createDynamicSchema = (provider, transactionType, extensions = {}) => {
const baseSchema = z.object({
body: z.object({
amount: z.number().positive(),
currency: z.string().length(3),
paymentMethod: z.object({
bankProvider: z.string(),
accountNumber: z.string().min(10)
})
})
});
if (provider === 'bncr') {
return baseSchema.extend({
body: z.object({
bncrSpecific: z.string().optional()
})
});
}
return null; // Proveedor no soportado
};
// Crear middleware
const validation = createDynamicSchemaValidation(
createDynamicSchema, // Función para crear schema dinámico
'payment', // Tipo de transacción
{}, // Extensiones personalizadas
'bncr-integration', // Nombre de la integración
getCustomValidation, // Función para validación personalizada (opcional)
null // Función de respuesta de error (opcional)
);
// Usar en Express
app.post('/transactions', validation, (req, res) => {
res.json({ success: true });
});
```
### Middleware de Validación Base
```javascript
const { createBaseSchemaValidation } = require('@gp_jcisneros/errors');
const { z } = require('zod');
const userSchema = z.object({
body: z.object({
email: z.string().email('Email debe ser válido'),
name: z.string().min(2, 'Nombre debe tener al menos 2 caracteres'),
age: z.number().min(18, 'Debe ser mayor de edad')
})
});
// Crear middleware
const validation = createBaseSchemaValidation(
userSchema, // Schema base de Zod
{}, // Extensiones personalizadas
'user-service', // Nombre de la integración
getCustomValidation, // Función para validación personalizada (opcional)
null // Función de respuesta de error (opcional)
);
// Usar en Express
app.post('/users', validation, (req, res) => {
res.json({ success: true });
});
```
### Conversión Manual de Errores de Zod
```javascript
const { convertZodErrorToValidationError } = require('@gp_jcisneros/errors');
const { z } = require('zod');
const schema = z.object({
email: z.string().email(),
name: z.string().min(2)
});
app.post('/custom-validation', async (req, res) => {
try {
await schema.parseAsync(req.body);
res.json({ success: true });
} catch (error) {
if (error instanceof z.ZodError) {
const validationError = convertZodErrorToValidationError(
error,
'custom-service'
);
return res.status(400).json({
code: 400,
status: "failed",
message: validationError.message,
error: {
code: validationError.errorCode,
message: validationError.description
},
additionalData: validationError.additionalData
});
}
res.status(500).json({ error: 'Internal server error' });
}
});
```
### Formato de Respuesta Estandarizado
Todas las respuestas de error siguen este formato:
```json
{
"code": 400,
"status": "failed",
"message": "Event object failed validation: email must be a valid email address",
"error": {
"code": "GP-1204",
"message": "Schema validation failed"
},
"additionalData": {
"integration": "bncr-integration",
"field": "email",
"value": "invalid-email",
"type": "VALIDATION_ERROR",
"validation": {
"errors": [
{
"field": "email",
"message": "email must be a valid email address",
"received": "invalid-email",
"expected": "valid email format"
}
],
"issues": [...],
"customValidation": {...}
}
}
}
```
### Códigos de Error
| Código | Descripción |
|--------|-------------|
| GP-1204 | Error de validación de schema |
| GP-1205 | Campo requerido faltante |
| GP-1206 | Proveedor no soportado |
| GP-500 | Error interno de validación |
### Integración con Diferentes Frameworks
#### Express
```javascript
const express = require('express');
const { createDynamicSchemaValidation } = require('@gp_jcisneros/errors');
const app = express();
// Middleware personalizado de respuesta de error
const customErrorResponse = (res, error) => {
return res.status(error.statusCode).json({
code: error.statusCode,
status: "failed",
message: error.message,
error: {
code: error.errorCode,
message: error.description
},
additionalData: error.additionalData
});
};
const validation = createDynamicSchemaValidation(
createDynamicSchema,
'payment',
{},
'my-service',
getCustomValidation,
customErrorResponse
);
app.post('/api/transaction', validation, (req, res) => {
res.json({ success: true });
});
```
#### Fastify
```javascript
const fastify = require('fastify');
const { createBaseSchemaValidation } = require('@gp_jcisneros/errors');
const app = fastify();
// Adaptador para Fastify
const fastifyErrorResponse = (reply, error) => {
return reply.status(error.statusCode).send({
code: error.statusCode,
status: "failed",
message: error.message,
error: {
code: error.errorCode,
message: error.description
},
additionalData: error.additionalData
});
};
const validation = createBaseSchemaValidation(
baseSchema,
{},
'fastify-service',
getCustomValidation,
fastifyErrorResponse
);
app.addHook('preHandler', validation);
```
## 📋 Ejemplos de Uso
### Validación con Errores Estandarizados
```javascript
const { ValidationError } = require('@gp_jcisneros/errors');
function validateUserData(userData) {
const errors = [];
if (!userData.email) {
errors.push(ValidationError.required('email'));
} else if (!isValidEmail(userData.email)) {
errors.push(ValidationError.invalidEmail('email', userData.email));
}
if (!userData.password) {
errors.push(ValidationError.required('password'));
} else if (userData.password.length < 8) {
errors.push(ValidationError.minLength('password', 8, userData.password));
}
// Verificar que todos los errores tienen campos requeridos
const validErrors = errors.filter(error => error.hasRequiredFields());
return validErrors;
}
```
### Error de Base de Datos
```javascript
const { DatabaseError } = require('@gp_jcisneros/errors');
async function getUserById(userId) {
try {
const user = await db.users.findById(userId);
if (!user) {
throw DatabaseError.notFound('users');
}
return user;
} catch (error) {
// El error ya tiene campos requeridos automáticos
console.log('Campos requeridos:', error.getRequiredFields());
throw error;
}
}
```
### Error de AWS
```javascript
const { AWSError } = require('@gp_jcisneros/errors');
async function getS3Object(bucket, key) {
try {
const result = await s3.getObject({ Bucket: bucket, Key: key }).promise();
return result.Body;
} catch (error) {
if (error.code === 'NoSuchKey') {
throw AWSError.s3('NoSuchKey', 'Object not found');
}
throw error;
}
}
```
### Error de Integración
```javascript
const { IntegrationError } = require('@gp_jcisneros/errors');
async function processPayment(paymentData) {
try {
const response = await cybersourceAPI.processPayment(paymentData);
if (response.error) {
throw IntegrationError.cybersource(
response.error.message,
response.error.code,
{ transactionId: paymentData.transactionId }
);
}
return response;
} catch (error) {
// El error ya tiene campos requeridos automáticos
console.log('Código de integración:', error.integrationCode);
throw error;
}
}
```
### Error HTTP Personalizado
```javascript
const { HttpError } = require('@gp_jcisneros/errors');
function validateUserAccess(user, resource) {
if (!user) {
throw HttpError.unauthorized('Usuario no autenticado');
}
if (!user.isActive) {
throw HttpError.forbidden('Usuario inactivo');
}
if (!user.hasPermission(resource)) {
throw HttpError.forbidden('Sin permisos para acceder al recurso');
}
return true;
}
```
## 🔮 Preparación para Transformación Empresarial
### **Campos requeridos disponibles para mapeo:**
```javascript
const error = new CustomError('Error de validación', {
errorCode: 'GP_VALIDATION_ERROR',
description: 'Error de validación en los datos de entrada',
integration: 'transaction-service',
statusCode: 400
});
// Los campos requeridos están disponibles para transformación
console.log(error.getRequiredFields());
// {
// errorCode: 'GP_VALIDATION_ERROR',
// description: 'Error de validación en los datos de entrada',
// integration: 'transaction-service'
// }
// Verificar si tiene todos los campos requeridos
if (error.hasRequiredFields()) {
// Listo para transformación empresarial
const businessCode = mapToBusinessCode(error.errorCode);
console.log('Código de negocio:', businessCode);
}
```
### **Formato JSON:**
```javascript
const jsonFormat = error.toJSON();
// {
// name: 'CustomError',
// message: 'Error de validación',
// statusCode: 400,
// timestamp: '2024-12-19T10:30:00.000Z',
// stack: '...',
// errorCode: 'GP_VALIDATION_ERROR',
// description: 'Error de validación en los datos de entrada',
// integration: 'transaction-service'
// }
```
## 🧪 Tests
```bash
# Ejecutar tests
npm test
# Ejecutar tests con coverage
npm test -- --coverage
# Ejecutar tests en modo watch
npm test -- --watch
```
### **Cobertura de Tests:**
- ✅ **59 tests pasando** (100% de éxito)
- ✅ **6 suites de tests** completas
- ✅ **Todas las clases** con tests completos
- ✅ **Campos requeridos** validados
- ✅ **Métodos de conversión** testeados
- ✅ **Nuevo constructor** completamente testeado
- ✅ **Métodos estáticos** validados
## 🚀 Despliegue Local
### Configuración del Token NPM
Para publicar el paquete localmente, primero configura tu token de npm:
```bash
# Configurar token de npm (reemplaza TU_TOKEN_AQUI con tu token real)
npm config set //registry.npmjs.org/:_authToken=TU_TOKEN_AQUI
# Verificar que estás autenticado
npm whoami
```
### Pasos para Publicar
```bash
# 1. Instalar dependencias
npm install
# 2. Ejecutar tests
npm test
# 3. Ejecutar linting
npm run lint
# 4. Verificar qué se va a publicar
npm pack
# 5. Publicar el paquete
npm publish --access public
```
### Verificar la Publicación
```bash
# Verificar que el paquete se publicó correctamente
npm info @gp_jcisneros/errors
# Instalar el paquete para probar
npm install @gp_jcisneros/errors
```
### Actualizar Versión
```bash
# Incrementar versión patch (1.0.0 -> 1.0.1)
npm version patch
# Incrementar versión minor (1.0.0 -> 1.1.0)
npm version minor
# Incrementar versión major (1.0.0 -> 2.0.0)
npm version major
# Publicar nueva versión
npm publish
```
## 📦 Publicación
```bash
# Lint y tests
npm run lint
npm test
# Publicar
npm publish
```
## 🤝 Contribución
1. Fork el repositorio
2. Crea una rama para tu feature (`git checkout -b feature/nueva-funcionalidad`)
3. Commit tus cambios (`git commit -am 'Agregar nueva funcionalidad'`)
4. Push a la rama (`git push origin feature/nueva-funcionalidad`)
5. Crea un Pull Request
## 📄 Licencia
MIT License - ver [LICENSE](LICENSE) para detalles.
## 🆘 Soporte
Para soporte técnico, contacta al equipo de GreenPay o crea un issue en el repositorio.
---
## 🎯 **Estado del Proyecto**
### **✅ Completado:**
- ✅ **Constructor flexible** con objeto de opciones
- ✅ **Métodos estáticos** para crear errores fácilmente
- ✅ **Campos requeridos estandarizados** en todas las clases
- ✅ **59 tests pasando** (100% de éxito)
- ✅ **Linter sin errores**
- ✅ **Ejemplos funcionando**
- ✅ **Documentación actualizada**
- ✅ **Preparado para producción**
### **🚀 Listo para:**
- ✅ **Publicación en NPM**
- ✅ **Integración en otros módulos**
- ✅ **Transformación empresarial**
- ✅ **Monitoreo y tracking**
### **🔄 Cambios en v1.0.1-dev:**
- ✅ **Nuevo constructor** con objeto de opciones
- ✅ **Métodos estáticos** `create()` y `createMinimal()`
- ✅ **Soporte para campos adicionales**
- ✅ **Mejor documentación** con JSDoc detallado
- ✅ **Tests actualizados** para el nuevo constructor
- ✅ **Status codes apropiados** para cada tipo de error
- ✅ **Middleware de validación Zod** completamente integrado
- ✅ **Conversión automática** de errores de Zod a ValidationError
- ✅ **Middleware reutilizable** para validación dinámica y base
- ✅ **Soporte multi-framework** (Express, Fastify, etc.)
- ✅ **Formato de respuesta estandarizado** para todos los errores
- ✅ **Documentación completa** del middleware de Zod