Skip to content
English
On this page

Ejercicio: Landing Page de Registro con AWS Amplify

Escenario

Desarrollaremos una landing page moderna para registro de usuarios utilizando AWS Amplify, con las siguientes características:

  • Frontend React con Amplify UI Components
  • Backend serverless con AppSync y DynamoDB
  • Autenticación con Cognito
  • Validación de formularios
  • Dashboard administrativo para consultar registros
  • Gestión de archivos con S3 (para fotos de perfil)
plaintext
landing-page-amplify/
├── src/
│   ├── components/
│   │   ├── RegisterForm/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Header/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   └── Footer/
│   │       ├── index.js
│   │       └── styles.css
│   │
│   ├── graphql/
│   │   ├── mutations.js
│   │   ├── queries.js
│   │   └── schema.graphql
│   │
│   ├── hooks/
│   │   └── useForm.js
│   │
│   ├── styles/
│   │   └── global.css
│   │
│   ├── utils/
│   │   ├── validators.js
│   │   └── constants.js
│   │
│   ├── App.js
│   └── index.js

├── amplify/
│   ├── backend/
│   │   ├── api/
│   │   ├── auth/
│   │   └── storage/
│   │
│   └── team-provider-info.json

├── public/
│   ├── index.html
│   └── assets/

├── tests/
│   ├── unit/
│   └── integration/

├── scripts/
│   ├── setup.sh
│   └── deploy.sh

├── package.json
└── README.md

Etapas del Ejercicio

Etapa 1: Configuración Inicial

  • Configurar proyecto React
  • Inicializar Amplify
  • Configurar autenticación con Cognito
  • Establecer estructura base del proyecto

Etapa 2: Modelado de Datos

  • Diseñar schema GraphQL
  • Configurar DynamoDB
  • Implementar resolvers
  • Configurar permisos de acceso

Etapa 3: Desarrollo Frontend

  • Crear componentes de UI
  • Implementar formulario de registro
  • Integrar validaciones
  • Estilizar interfaz

Etapa 4: Integración Backend

  • Conectar con AppSync
  • Implementar mutaciones y queries
  • Manejar estados y errores
  • Configurar storage para archivos

Etapa 5: Testing y Despliegue

  • Implementar pruebas unitarias
  • Configurar pipeline CI/CD
  • Desplegar a producción
  • Configurar monitoreo

Etapa 1: Configuración Inicial

Objetivos:

  1. Configurar el proyecto base con React
  2. Inicializar AWS Amplify
  3. Configurar autenticación con Cognito
  4. Establecer estructura base del proyecto

1.1 Inicialización del Proyecto

Primero, creamos el proyecto base:

bash
# Crear proyecto React
npx create-react-app landing-page-amplify
cd landing-page-amplify

# Instalar dependencias
npm install @aws-amplify/ui-react aws-amplify @emotion/react

1.2 Configuración de Amplify

Inicializar Amplify en el proyecto:

bash
# Instalar Amplify CLI
npm install -g @aws-amplify/cli

# Inicializar Amplify
amplify init

# Agregar autenticación 
amplify add auth

# Agregar API GraphQL
amplify add api

# Push configuración
amplify push

1.3 Componentes Base

tsx
import { Amplify } from 'aws-amplify';
import { ThemeProvider } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import awsconfig from './aws-exports';

Amplify.configure(awsconfig);

const theme = {
  name: 'landing-theme',
  tokens: {
    colors: {
      primary: {
        10: '#f5f5f5',
        20: '#e5e5e5',
        40: '#bdbdbd',
        60: '#757575',
        80: '#424242',
        90: '#212121',
      }
    },
    components: {
      button: {
        primary: {
          backgroundColor: '#2196f3',
          color: 'white',
          _hover: {
            backgroundColor: '#1976d2',
          }
        }
      },
      textfield: {
        borderRadius: '4px'
      }
    }
  }
};

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <div className="app">
        <header className="app-header">
          <h1>Registro de Usuario</h1>
        </header>
        <main className="app-content">
          {/* Aquí irá el formulario de registro */}
        </main>
        <footer className="app-footer">
          <p>© 2024 Landing Page Demo</p>
        </footer>
      </div>
    </ThemeProvider>
  );
}

1.4 Configuración de AWS

javascript
// amplify/backend/auth/cognitoConfig.js
export const cognitoConfig = {
  userPoolConfig: {
    userPoolName: 'landing-page-users',
    passwordPolicy: {
      minimumLength: 8,
      requireLowercase: true,
      requireUppercase: true,
      requireNumbers: true,
      requireSymbols: true
    },
    signupAttributes: ['email', 'name', 'phone_number'],
    autoVerifiedAttributes: ['email'],
    mfaConfiguration: 'OFF',
  },
  webClientConfig: {
    clientName: 'landing-page-client',
    generateSecret: false,
    refreshTokenValidity: 30,
    accessTokenValidity: 1,
    idTokenValidity: 1,
    preventUserExistenceErrors: true
  }
};

// amplify/backend/api/schema.graphql
type User @model @auth(rules: [{allow: owner}]) {
  id: ID!
  email: String!
  name: String!
  phone: String
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
}

1.5 Scripts de Configuración

bash
#!/bin/bash
# scripts/setup.sh

echo "Configurando ambiente de desarrollo..."

# Verificar dependencias
if ! command -v node &> /dev/null; then
    echo "Node.js no encontrado. Por favor instálalo primero."
    exit 1
fi

if ! command -v amplify &> /dev/null; then
    echo "Instalando Amplify CLI..."
    npm install -g @aws-amplify/cli
fi

# Instalar dependencias del proyecto
npm install

# Inicializar Amplify
amplify init \
  --amplify "{\"projectName\":\"landingpage\",\"envName\":\"dev\"}" \
  --frontend "{\"framework\":\"react\",\"config\":{\"SourceDir\":\"src\",\"DistributionDir\":\"build\",\"BuildCommand\":\"npm run-script build\",\"StartCommand\":\"npm run-script start\"}}" \
  --yes

# Agregar autenticación
amplify add auth \
  --service cognito \
  --providerPlugin amplify-auth-cognito \
  --dependsOn [] \
  --frontendConfig "{\"mandatorySignIn\":false}" \
  --yes

# Agregar API
amplify add api \
  --service graphql \
  --providerPlugin amplify-api-graphql \
  --apiName landingPageAPI \
  --schemaPath amplify/backend/api/schema.graphql \
  --yes

echo "Configuración completada exitosamente!"

Verificación de la Etapa 1

Checklist de Configuración:

  • [ ] Proyecto React creado e inicializado
  • [ ] Amplify CLI instalado y configurado
  • [ ] Cognito configurado para autenticación
  • [ ] Estructura de directorios creada
  • [ ] Dependencias instaladas
  • [ ] Theme básico implementado
  • [ ] Scripts de configuración funcionando

Pruebas Iniciales:

  1. Ejecutar npm start para verificar que la aplicación corre
  2. Verificar conexión con Amplify usando amplify status
  3. Comprobar que Cognito está configurado correctamente
  4. Validar que el tema se aplica correctamente

Troubleshooting Común:

  1. Error de Amplify Init: Verificar credenciales de AWS
  2. Problemas de Dependencias: Limpiar node_modules y reinstalar
  3. Errores de Cognito: Revisar configuración en AWS Console
  4. Problemas de Permisos: Verificar rol IAM

Etapa 2: Modelado de Datos

Objetivos:

  1. Diseñar y configurar el schema GraphQL
  2. Configurar DynamoDB para almacenamiento
  3. Implementar resolvers personalizados
  4. Configurar los permisos de acceso

2.1 Schema GraphQL

graphql
type User 
  @model 
  @auth(rules: [
    { allow: owner },
    { allow: groups, groups: ["Admin"], operations: [read, update, delete] }
  ]) {
  id: ID!
  email: String!
  name: String!
  phoneNumber: String
  profilePicture: String
  address: Address
  interests: [String]
  status: UserStatus
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
}

type Address {
  street: String!
  city: String!
  state: String!
  country: String!
  zipCode: String!
}

enum UserStatus {
  PENDING
  ACTIVE
  BLOCKED
}

type UserStats 
  @model
  @auth(rules: [{ allow: groups, groups: ["Admin"] }]) {
  totalUsers: Int!
  activeUsers: Int!
  lastUpdateDate: AWSDateTime!
}

input RegisterUserInput {
  email: String!
  name: String!
  phoneNumber: String
  address: AddressInput
  interests: [String]
}

input AddressInput {
  street: String!
  city: String!
  state: String!
  country: String!
  zipCode: String!
}

type Query {
  getUsersByStatus(status: UserStatus!): [User]
    @function(name: "getUsersByStatus")
    @auth(rules: [{ allow: groups, groups: ["Admin"] }])
  
  searchUsers(searchTerm: String!): [User]
    @function(name: "searchUsers")
    @auth(rules: [{ allow: groups, groups: ["Admin"] }])
}

type Mutation {
  registerUser(input: RegisterUserInput!): User
    @function(name: "registerUser")
  
  updateUserStatus(userId: ID!, status: UserStatus!): User
    @function(name: "updateUserStatus")
    @auth(rules: [{ allow: groups, groups: ["Admin"] }])
}

type Subscription {
  onNewUserRegistered: User
    @aws_subscribe(mutations: ["registerUser"])
    @auth(rules: [{ allow: groups, groups: ["Admin"] }])
}

2.2 Configuración DynamoDB

javascript
// amplify/backend/api/landingPageAPI/dynamodb-config.js
export const tableConfig = {
  UserTable: {
    tableName: 'User-${env}',
    partitionKey: {
      name: 'id',
      type: 'S'
    },
    sortKey: {
      name: 'email',
      type: 'S'
    },
    gsi1: {
      name: 'StatusIndex',
      partitionKey: {
        name: 'status',
        type: 'S'
      },
      sortKey: {
        name: 'createdAt',
        type: 'S'
      }
    },
    gsi2: {
      name: 'EmailIndex',
      partitionKey: {
        name: 'email',
        type: 'S'
      }
    },
    ttl: {
      attributeName: 'ttl',
      enabled: true
    },
    stream: {
      enabled: true,
      viewType: 'NEW_AND_OLD_IMAGES'
    },
    billingMode: 'PAY_PER_REQUEST',
    pointInTimeRecovery: true,
    encryption: 'AWS_OWNED_KMS_KEY',
    tags: {
      Environment: '${env}',
      Project: 'LandingPage'
    }
  }
}

2.3 Implementación de Resolvers

javascript
// amplify/backend/api/landingPageAPI/resolvers/Mutation.registerUser.js
export const handler = async (event) => {
  const { arguments, identity } = event;
  const { input } = arguments;
  
  try {
    // Validar input
    if (!input.email || !input.name) {
      throw new Error('Email and name are required');
    }
    
    // Crear usuario en DynamoDB
    const user = {
      id: uuid(),
      email: input.email.toLowerCase(),
      name: input.name,
      phoneNumber: input.phoneNumber,
      address: input.address,
      interests: input.interests || [],
      status: 'PENDING',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };
    
    const params = {
      TableName: process.env.USER_TABLE,
      Item: user,
      ConditionExpression: 'attribute_not_exists(email)'
    };
    
    await dynamoDB.put(params).promise();
    
    // Disparar eventos y notificaciones
    await publishToEventBridge({
      detailType: 'UserRegistered',
      detail: { userId: user.id, email: user.email }
    });
    
    return user;
  } catch (error) {
    console.error('Error registering user:', error);
    throw error;
  }
};

// amplify/backend/api/landingPageAPI/resolvers/Query.getUsersByStatus.js
export const handler = async (event) => {
  const { arguments } = event;
  const { status } = arguments;
  
  try {
    const params = {
      TableName: process.env.USER_TABLE,
      IndexName: 'StatusIndex',
      KeyConditionExpression: '#status = :status',
      ExpressionAttributeNames: {
        '#status': 'status'
      },
      ExpressionAttributeValues: {
        ':status': status
      }
    };
    
    const result = await dynamoDB.query(params).promise();
    return result.Items;
  } catch (error) {
    console.error('Error getting users by status:', error);
    throw error;
  }
};

2.4 Configuración IAM

yaml
Resources:
  UserTableIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: appsync.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: UserTableAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:DeleteItem
                  - dynamodb:Query
                  - dynamodb:Scan
                Resource: 
                  - !GetAtt UserTable.Arn
                  - !Sub ${UserTable.Arn}/index/*
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*

  AdminGroupRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: cognito-idp.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSAppSyncInvokeFullAccess
      Policies:
        - PolicyName: AdminAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:*
                Resource: !GetAtt UserTable.Arn

Verificación de la Etapa 2

Checklist de Modelado:

  • [ ] Schema GraphQL implementado
  • [ ] Tabla DynamoDB configurada
  • [ ] Resolvers funcionando
  • [ ] Permisos IAM establecidos
  • [ ] Índices configurados
  • [ ] Queries y mutaciones probadas
  • [ ] Suscripciones configuradas

Pruebas Recomendadas:

  1. Probar creación de usuarios
  2. Verificar consultas por status
  3. Comprobar búsquedas
  4. Validar permisos de admin

Troubleshooting Común:

  1. Errores de Schema: Verificar sintaxis GraphQL
  2. Problemas de Permisos: Revisar roles IAM
  3. Errores de DynamoDB: Comprobar índices y capacidad
  4. Problemas de Resolvers: Verificar logs de CloudWatch

Etapa 3: Desarrollo Frontend

Objetivos:

  1. Crear los componentes de UI
  2. Implementar el formulario de registro
  3. Añadir validaciones
  4. Estilizar la interfaz usando Amplify UI

3.1 Componentes Base

tsx
import { useState } from 'react';
import { 
  Card,
  Heading,
  TextField,
  Button,
  SelectField,
  PhoneNumberField,
  Alert,
  Flex
} from '@aws-amplify/ui-react';
import { useForm } from '../../hooks/useForm';

export default function RegisterForm() {
  const [error, setError] = useState(null);
  const { values, handleChange, handleSubmit, isSubmitting } = useForm({
    initialValues: {
      name: '',
      email: '',
      phoneNumber: '',
      interests: [],
      address: {
        street: '',
        city: '',
        state: '',
        country: '',
        zipCode: ''
      }
    },
    onSubmit: async (values) => {
      try {
        setError(null);
        // Lógica de registro se implementará en la siguiente etapa
      } catch (err) {
        setError(err.message);
      }
    }
  });

  return (
    <Card variation="elevated" className="max-w-xl mx-auto p-6">
      <Heading level={3} className="mb-6">Registro de Usuario</Heading>
      
      {error && (
        <Alert variation="error" className="mb-4">
          {error}
        </Alert>
      )}
      
      <form onSubmit={handleSubmit} className="space-y-4">
        <TextField
          label="Nombre Completo"
          name="name"
          value={values.name}
          onChange={handleChange}
          required
          placeholder="Ingresa tu nombre completo"
        />
        
        <TextField
          label="Correo Electrónico"
          name="email"
          type="email"
          value={values.email}
          onChange={handleChange}
          required
          placeholder="correo@ejemplo.com"
        />
        
        <PhoneNumberField
          label="Teléfono"
          name="phoneNumber"
          value={values.phoneNumber}
          onChange={handleChange}
          placeholder="+1 (555) 555-5555"
        />
        
        <Heading level={5} className="mt-6">Dirección</Heading>
        
        <TextField
          label="Calle"
          name="address.street"
          value={values.address.street}
          onChange={handleChange}
          placeholder="Calle Principal 123"
        />
        
        <Flex direction="row" gap="1rem">
          <TextField
            label="Ciudad"
            name="address.city"
            value={values.address.city}
            onChange={handleChange}
          />
          
          <TextField
            label="Estado/Provincia"
            name="address.state"
            value={values.address.state}
            onChange={handleChange}
          />
        </Flex>
        
        <Flex direction="row" gap="1rem">
          <TextField
            label="País"
            name="address.country"
            value={values.address.country}
            onChange={handleChange}
          />
          
          <TextField
            label="Código Postal"
            name="address.zipCode"
            value={values.address.zipCode}
            onChange={handleChange}
          />
        </Flex>
        
        <SelectField
          label="Intereses"
          name="interests"
          value={values.interests}
          onChange={handleChange}
          multiple
        >
          <option value="tecnologia">Tecnología</option>
          <option value="negocios">Negocios</option>
          <option value="deportes">Deportes</option>
          <option value="arte">Arte y Cultura</option>
          <option value="ciencia">Ciencia</option>
        </SelectField>
        
        <Button
          type="submit"
          isLoading={isSubmitting}
          loadingText="Registrando..."
          className="w-full"
        >
          Registrarse
        </Button>
      </form>
    </Card>
  );
}

3.2 Custom Hook para Formularios

javascript
// src/hooks/useForm.js
import { useState } from 'react';

export function useForm({ initialValues, onSubmit, validate }) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = (event) => {
    const { name, value } = event.target;
    
    // Manejar campos anidados (e.g., address.street)
    if (name.includes('.')) {
      const [parent, child] = name.split('.');
      setValues(prev => ({
        ...prev,
        [parent]: {
          ...prev[parent],
          [child]: value
        }
      }));
    } else {
      setValues(prev => ({
        ...prev,
        [name]: value
      }));
    }

    // Validar campo cuando cambia
    if (validate) {
      const fieldError = validate({ [name]: value });
      setErrors(prev => ({
        ...prev,
        [name]: fieldError?.[name]
      }));
    }
  };

  const handleBlur = (event) => {
    const { name } = event.target;
    setTouched(prev => ({
      ...prev,
      [name]: true
    }));
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    
    setIsSubmitting(true);

    // Validar todo el formulario
    if (validate) {
      const formErrors = validate(values);
      setErrors(formErrors);
      if (Object.keys(formErrors).length > 0) {
        setIsSubmitting(false);
        return;
      }
    }

    try {
      await onSubmit(values);
      // Resetear formulario después de envío exitoso
      setValues(initialValues);
      setTouched({});
      setErrors({});
    } catch (error) {
      setErrors(prev => ({
        ...prev,
        submit: error.message
      }));
    } finally {
      setIsSubmitting(false);
    }
  };

  return {
    values,
    errors,
    touched,
    isSubmitting,
    handleChange,
    handleBlur,
    handleSubmit
  };
}

3.3 Validaciones

javascript
// src/utils/validators.js
export const validateEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email) return 'El correo es requerido';
  if (!emailRegex.test(email)) return 'El correo no es válido';
  return null;
};

export const validatePhone = (phone) => {
  const phoneRegex = /^\+?[\d\s-]{10,}$/;
  if (phone && !phoneRegex.test(phone)) {
    return 'El número de teléfono no es válido';
  }
  return null;
};

export const validatePostalCode = (code, country) => {
  const postalRegexByCountry = {
    US: /^\d{5}(-\d{4})?$/,
    MX: /^\d{5}$/,
    ES: /^\d{5}$/,
  };

  if (code && postalRegexByCountry[country]) {
    if (!postalRegexByCountry[country].test(code)) {
      return 'Código postal no válido para el país seleccionado';
    }
  }
  return null;
};

export const validateRegistrationForm = (values) => {
  const errors = {};

  // Validar nombre
  if (!values.name) {
    errors.name = 'El nombre es requerido';
  } else if (values.name.length < 2) {
    errors.name = 'El nombre debe tener al menos 2 caracteres';
  }

  // Validar email
  const emailError = validateEmail(values.email);
  if (emailError) errors.email = emailError;

  // Validar teléfono
  if (values.phoneNumber) {
    const phoneError = validatePhone(values.phoneNumber);
    if (phoneError) errors.phoneNumber = phoneError;
  }

  // Validar dirección
  if (values.address) {
    if (values.address.zipCode && values.address.country) {
      const postalError = validatePostalCode(values.address.zipCode, values.address.country);
      if (postalError) errors['address.zipCode'] = postalError;
    }
  }

  return errors;
};

3.4 Estilos Globales

css
/* src/styles/global.css */
:root {
  --amplify-colors-background-primary: #ffffff;
  --amplify-colors-background-secondary: #f5f5f5;
  --amplify-colors-brand-primary-10: #f3f9ff;
  --amplify-colors-brand-primary-80: #0066cc;
  --amplify-colors-brand-primary-90: #004d99;
  --amplify-colors-brand-primary-100: #003366;
  --amplify-colors-font-interactive: var(--amplify-colors-brand-primary-80);
  --amplify-components-button-primary-background-color: var(--amplify-colors-brand-primary-80);
  --amplify-components-button-primary-hover-background-color: var(--amplify-colors-brand-primary-90);
  --amplify-components-button-primary-focus-background-color: var(--amplify-colors-brand-primary-90);
}

.form-container {
  max-width: 600px;
  margin: 2rem auto;
  padding: 2rem;
  background: var(--amplify-colors-background-primary);
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.form-header {
  text-align: center;
  margin-bottom: 2rem;
}

.form-section {
  margin-bottom: 1.5rem;
}

.error-message {
  color: var(--amplify-colors-red-60);
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.success-message {
  color: var(--amplify-colors-green-60);
  text-align: center;
  padding: 1rem;
  margin-bottom: 1rem;
  background-color: var(--amplify-colors-green-10);
  border-radius: 4px;
}

@media (max-width: 640px) {
  .form-container {
    margin: 1rem;
    padding: 1rem;
  }
}

Verificación de la Etapa 3

Checklist de Frontend:

  • [ ] Componentes creados y funcionando
  • [ ] Formulario implementado
  • [ ] Validaciones activas
  • [ ] Estilos aplicados
  • [ ] Responsive design
  • [ ] Manejo de errores
  • [ ] UX coherente

Pruebas Recomendadas:

  1. Probar validaciones en tiempo real
  2. Verificar responsividad
  3. Comprobar mensajes de error
  4. Validar accesibilidad

Troubleshooting Común:

  1. Problemas de Estilo: Verificar importaciones CSS
  2. Errores de Validación: Revisar regex y lógica
  3. Problemas de Rendimiento: Optimizar renders
  4. Inconsistencias de UI: Revisar tema de Amplify

Etapa 4: Integración Backend y Servicios AWS

Objetivos:

  1. Integrar AppSync para operaciones GraphQL
  2. Configurar almacenamiento de archivos con S3
  3. Implementar notificaciones con SNS
  4. Configurar métricas y logs con CloudWatch

4.1 Integración GraphQL

javascript
// src/graphql/mutations.js
import { graphql } from '@aws-amplify/api-graphql';

export const registerUser = /* GraphQL */ `
  mutation RegisterUser($input: RegisterUserInput!) {
    registerUser(input: $input) {
      id
      email
      name
      phoneNumber
      address {
        street
        city
        state
        country
        zipCode
      }
      interests
      status
      createdAt
      updatedAt
    }
  }
`;

// src/services/UserService.js
import { API, graphqlOperation } from 'aws-amplify';
import * as mutations from '../graphql/mutations';
import * as queries from '../graphql/queries';

export class UserService {
  static async registerUser(userData) {
    try {
      const { data } = await API.graphql(
        graphqlOperation(mutations.registerUser, { input: userData })
      );
      return data.registerUser;
    } catch (error) {
      console.error('Error registering user:', error);
      throw new Error('Error al registrar usuario. Por favor intente nuevamente.');
    }
  }

  static async getUsersByStatus(status) {
    try {
      const { data } = await API.graphql(
        graphqlOperation(queries.getUsersByStatus, { status })
      );
      return data.getUsersByStatus;
    } catch (error) {
      console.error('Error fetching users:', error);
      throw new Error('Error al obtener usuarios.');
    }
  }
}

4.2 Configuración S3

javascript
// src/services/StorageService.js
import { Storage } from 'aws-amplify';
import { v4 as uuid } from 'uuid';

export class StorageService {
  static async uploadProfilePicture(file, userId) {
    try {
      const extension = file.name.split('.').pop();
      const key = `profiles/${userId}/${uuid()}.${extension}`;
      
      const result = await Storage.put(key, file, {
        contentType: file.type,
        metadata: {
          userId,
          uploadDate: new Date().toISOString()
        }
      });

      const url = await Storage.get(key);
      return url;
    } catch (error) {
      console.error('Error uploading file:', error);
      throw new Error('Error al subir la imagen. Por favor intente nuevamente.');
    }
  }

  static async deleteProfilePicture(key) {
    try {
      await Storage.remove(key);
    } catch (error) {
      console.error('Error deleting file:', error);
      throw new Error('Error al eliminar la imagen.');
    }
  }
}

// Configuración de S3 en Amplify
export const storageConfig = {
  Storage: {
    AWSS3: {
      bucket: 'user-profile-pictures-${env}',
      region: process.env.REGION,
      uploadTimeout: 300000
    }
  }
};

4.3 Notificaciones SNS

javascript
// src/services/NotificationService.js
import { SNS } from 'aws-sdk';
import { getUserPreferences } from './UserPreferencesService';

export class NotificationService {
  constructor() {
    this.sns = new SNS({
      region: process.env.REGION,
      apiVersion: '2010-03-31'
    });
  }

  async sendWelcomeNotification(userId, email) {
    try {
      const params = {
        Message: JSON.stringify({
          default: `Nuevo usuario registrado: ${email}`,
          email: `
            <h2>¡Bienvenido a nuestra plataforma!</h2>
            <p>Gracias por registrarte. Tu cuenta ha sido creada exitosamente.</p>
            <p>Por favor verifica tu correo electrónico para activar todas las funcionalidades.</p>
          `,
          sms: `Bienvenido a nuestra plataforma. Por favor verifica tu correo electrónico.`
        }),
        MessageStructure: 'json',
        TopicArn: process.env.WELCOME_NOTIFICATION_TOPIC_ARN
      };

      await this.sns.publish(params).promise();
    } catch (error) {
      console.error('Error sending welcome notification:', error);
    }
  }

  async sendAdminNotification(eventType, data) {
    try {
      const params = {
        Message: JSON.stringify({
          eventType,
          data,
          timestamp: new Date().toISOString()
        }),
        TopicArn: process.env.ADMIN_NOTIFICATION_TOPIC_ARN
      };

      await this.sns.publish(params).promise();
    } catch (error) {
      console.error('Error sending admin notification:', error);
    }
  }
}

4.4 Monitoreo CloudWatch

javascript
// src/services/MonitoringService.js
import { CloudWatch } from 'aws-sdk';

export class MonitoringService {
  constructor() {
    this.cloudWatch = new CloudWatch({
      region: process.env.REGION,
      apiVersion: '2010-08-01'
    });
  }

  async logRegistrationMetrics(success, duration) {
    try {
      const metrics = [
        {
          MetricName: 'UserRegistrationAttempt',
          Value: 1,
          Unit: 'Count',
          Dimensions: [
            {
              Name: 'Status',
              Value: success ? 'Success' : 'Failure'
            }
          ]
        },
        {
          MetricName: 'RegistrationDuration',
          Value: duration,
          Unit: 'Milliseconds',
          Dimensions: [
            {
              Name: 'Operation',
              Value: 'UserRegistration'
            }
          ]
        }
      ];

      await this.cloudWatch.putMetricData({
        Namespace: 'LandingPage/UserRegistration',
        MetricData: metrics
      }).promise();
    } catch (error) {
      console.error('Error logging metrics:', error);
    }
  }

  async trackAPILatency(operation, duration) {
    try {
      await this.cloudWatch.putMetricData({
        Namespace: 'LandingPage/API',
        MetricData: [{
          MetricName: 'OperationLatency',
          Value: duration,
          Unit: 'Milliseconds',
          Dimensions: [
            {
              Name: 'Operation',
              Value: operation
            }
          ]
        }]
      }).promise();
    } catch (error) {
      console.error('Error tracking API latency:', error);
    }
  }
}

// Middleware para tracking
export const trackingMiddleware = store => next => action => {
  const start = Date.now();
  const result = next(action);
  const duration = Date.now() - start;

  if (action.type.startsWith('api/')) {
    const monitoring = new MonitoringService();
    monitoring.trackAPILatency(action.type, duration);
  }

  return result;
};

Verificación de la Etapa 4

Checklist de Integración:

  • [ ] Operaciones GraphQL funcionando
  • [ ] S3 configurado y probado
  • [ ] Notificaciones SNS activas
  • [ ] Métricas CloudWatch registradas
  • [ ] Manejo de errores implementado
  • [ ] Performance optimizado
  • [ ] Seguridad configurada

Pruebas Recomendadas:

  1. Probar operaciones CRUD
  2. Verificar subida de archivos
  3. Comprobar notificaciones
  4. Validar métricas

Troubleshooting Común:

  1. Errores de GraphQL: Verificar schema y resolvers
  2. Problemas de S3: Revisar permisos y CORS
  3. Errores de SNS: Verificar roles IAM
  4. Problemas de CloudWatch: Comprobar métricas y logs

Etapa 5: Testing, Despliegue y CI/CD

Objetivos:

  1. Implementar pruebas completas
  2. Configurar pipeline CI/CD con AWS
  3. Establecer monitoreo en producción
  4. Documentar el proceso de despliegue

5.1 Suite de Pruebas

javascript
// tests/unit/RegisterForm.test.js
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { RegisterForm } from '../../src/components/RegisterForm';
import { UserService } from '../../src/services/UserService';
import '@testing-library/jest-dom';

jest.mock('../../src/services/UserService');

describe('RegisterForm', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('renders form fields correctly', () => {
    render(<RegisterForm />);
    
    expect(screen.getByLabelText(/nombre completo/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/correo electrónico/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/teléfono/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /registrarse/i })).toBeInTheDocument();
  });

  test('validates required fields', async () => {
    render(<RegisterForm />);
    
    fireEvent.click(screen.getByRole('button', { name: /registrarse/i }));

    await waitFor(() => {
      expect(screen.getByText(/el nombre es requerido/i)).toBeInTheDocument();
      expect(screen.getByText(/el correo es requerido/i)).toBeInTheDocument();
    });
  });

  test('submits form successfully', async () => {
    UserService.registerUser.mockResolvedValueOnce({
      id: '123',
      email: 'test@example.com',
      name: 'Test User'
    });

    render(<RegisterForm />);

    fireEvent.change(screen.getByLabelText(/nombre completo/i), {
      target: { value: 'Test User' }
    });
    fireEvent.change(screen.getByLabelText(/correo electrónico/i), {
      target: { value: 'test@example.com' }
    });
    fireEvent.change(screen.getByLabelText(/teléfono/i), {
      target: { value: '+1234567890' }
    });

    fireEvent.click(screen.getByRole('button', { name: /registrarse/i }));

    await waitFor(() => {
      expect(UserService.registerUser).toHaveBeenCalledWith({
        name: 'Test User',
        email: 'test@example.com',
        phoneNumber: '+1234567890'
      });
    });
  });
});

// tests/integration/registration.test.js
import { test, expect } from '@playwright/test';

test.describe('Registration Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/register');
  });

  test('complete registration process', async ({ page }) => {
    await page.fill('[name="name"]', 'Integration Test User');
    await page.fill('[name="email"]', 'test@integration.com');
    await page.fill('[name="phoneNumber"]', '+1234567890');

    await page.click('button[type="submit"]');

    await expect(page.locator('.success-message')).toBeVisible();
    await expect(page.locator('.success-message')).toContainText('registro exitoso');
  });

  test('handles validation errors', async ({ page }) => {
    await page.fill('[name="email"]', 'invalid-email');
    await page.click('button[type="submit"]');

    await expect(page.locator('.error-message')).toBeVisible();
    await expect(page.locator('.error-message')).toContainText('correo no es válido');
  });
});

5.2 Configuración CI/CD

yaml
# buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 16
    commands:
      - npm ci
      
  pre_build:
    commands:
      - echo "Running tests..."
      - npm run test:unit
      - npm run test:integration
      - echo "Running linting..."
      - npm run lint
      
  build:
    commands:
      - echo "Building application..."
      - npm run build
      - echo "Running security audit..."
      - npm audit
      
  post_build:
    commands:
      - echo "Running E2E tests..."
      - npm run test:e2e
      - echo "Preparing deployment package..."
      - aws s3 sync build/ s3://${DEPLOY_BUCKET} --delete

artifacts:
  files:
    - '**/*'
  base-directory: build

cache:
  paths:
    - 'node_modules/**/*'

# cloudformation/pipeline.yml
Resources:
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole

  LandingPagePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: landing-page-pipeline
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: '1'
              Configuration:
                RepositoryName: !Ref RepositoryName
                BranchName: main
              OutputArtifacts:
                - Name: SourceOutput
              
        - Name: Test
          Actions:
            - Name: UnitTest
              ActionTypeId:
                Category: Test
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              Configuration:
                ProjectName: !Ref UnitTestProject
              InputArtifacts:
                - Name: SourceOutput
                
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              Configuration:
                ProjectName: !Ref BuildProject
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput
                
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: S3
                Version: '1'
              Configuration:
                BucketName: !Ref DeploymentBucket
                Extract: true
              InputArtifacts:
                - Name: BuildOutput

5.3 Scripts de Despliegue

bash
#!/bin/bash
# scripts/deploy.sh

set -e

ENVIRONMENT=$1
REGION="us-east-1"
STACK_NAME="landing-page-${ENVIRONMENT}"
TEMPLATE_FILE="cloudformation/template.yml"

# Validar ambiente
if [[ ! "$ENVIRONMENT" =~ ^(dev|stg|prod)$ ]]; then
    echo "Environment must be dev, stg, or prod"
    exit 1
fi

# Validar template
echo "Validating CloudFormation template..."
aws cloudformation validate-template \
    --template-body file://$TEMPLATE_FILE \
    --region $REGION

# Crear/Actualizar stack
echo "Deploying to $ENVIRONMENT..."
aws cloudformation deploy \
    --template-file $TEMPLATE_FILE \
    --stack-name $STACK_NAME \
    --parameter-overrides \
        Environment=$ENVIRONMENT \
        DomainName="landing-${ENVIRONMENT}.example.com" \
    --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
    --region $REGION \
    --tags Environment=$ENVIRONMENT Application=LandingPage

# Configurar DNS y CDN
echo "Configuring DNS and CDN..."
DISTRIBUTION_ID=$(aws cloudfront list-distributions \
    --query "DistributionList.Items[?Aliases.Items[?contains(@,'landing-${ENVIRONMENT}')]].Id" \
    --output text)

if [ ! -z "$DISTRIBUTION_ID" ]; then
    echo "Invalidating CloudFront cache..."
    aws cloudfront create-invalidation \
        --distribution-id $DISTRIBUTION_ID \
        --paths "/*"
fi

echo "Deployment complete!"

# Ejecutar pruebas post-deployment
echo "Running post-deployment tests..."
npm run test:e2e -- --config baseUrl=https://landing-${ENVIRONMENT}.example.com

# Verificar métricas iniciales
echo "Checking initial metrics..."
aws cloudwatch get-metric-statistics \
    --namespace "AWS/ApiGateway" \
    --metric-name "4XXError" \
    --dimensions Name=ApiName,Value=landing-page-${ENVIRONMENT} \
    --start-time $(date -u +"%Y-%m-%dT%H:%M:%SZ" -d "5 minutes ago") \
    --end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
    --period 300 \
    --statistics Sum

echo "Deployment verification complete!"

5.4 Monitoreo de Producción

yaml
# cloudformation/monitoring.yml
Resources:
  ApplicationDashboard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardName: !Sub ${AWS::StackName}-dashboard
      DashboardBody: !Sub |
        {
          "widgets": [
            {
              "type": "metric",
              "properties": {
                "metrics": [
                  ["LandingPage", "RegistrationAttempts", "Environment", "${Environment}"],
                  [".", "RegistrationSuccess", ".", "."],
                  [".", "RegistrationFailure", ".", "."]
                ],
                "period": 300,
                "stat": "Sum",
                "region": "${AWS::Region}",
                "title": "Registration Metrics"
              }
            },
            {
              "type": "metric",
              "properties": {
                "metrics": [
                  ["AWS/ApiGateway", "4XXError", "ApiName", "${ApiName}"],
                  [".", "5XXError", ".", "."],
                  [".", "Latency", ".", "."]
                ],
                "period": 300,
                "stat": "Average",
                "region": "${AWS::Region}",
                "title": "API Metrics"
              }
            }
          ]
        }

  HighErrorRateAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub ${AWS::StackName}-high-error-rate
      AlarmDescription: High API error rate detected
      MetricName: 5XXError
      Namespace: AWS/ApiGateway
      Statistic: Sum
      Period: 300
      EvaluationPeriods: 2
      Threshold: 5
      ComparisonOperator: GreaterThanThreshold
      AlarmActions:
        - !Ref AlertsTopic
      Dimensions:
        - Name: ApiName
          Value: !Ref ApiName

  SlowResponseAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Sub ${AWS::StackName}-slow-response
      AlarmDescription: API response time is too high
      MetricName: Latency
      Namespace: AWS/ApiGateway
      Statistic: Average
      Period: 300
      EvaluationPeriods: 3
      Threshold: 1000
      ComparisonOperator: GreaterThanThreshold
      AlarmActions:
        - !Ref AlertsTopic

  AlertsTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub ${AWS::StackName}-alerts
      Subscription:
        - Protocol: email
          Endpoint: !Ref AlertsEmail

Verificación Final

Checklist:

  • [ ] Tests implementados y pasando
  • [ ] Pipeline CI/CD configurado
  • [ ] Scripts de despliegue probados
  • [ ] Monitoreo configurado
  • [ ] Alertas funcionando
  • [ ] Documentación completa
  • [ ] Proceso de rollback probado

Pruebas Finales:

  1. Ejecutar suite completa de pruebas
  2. Verificar pipeline end-to-end
  3. Validar monitoreo en producción
  4. Probar sistema de alertas

Troubleshooting Común:

  1. Errores de Pipeline: Verificar permisos y configuraciones
  2. Fallos de Tests: Revisar cobertura y casos edge
  3. Problemas de Despliegue: Validar CloudFormation
  4. Alertas Falsas: Ajustar umbrales

Perfecto, ahora proporcionaré un resumen final del proyecto y las consideraciones clave para operar y mantener la aplicación en producción.

Resumen Final y Guía de Operaciones

1. Descripción General del Sistema

markdown
# Arquitectura de Landing Page de Registro

## Componentes Principales

### Frontend
- React SPA hosteada en Amplify
- UI Components personalizados con Amplify UI
- Manejo de formularios y validaciones client-side
- Integración con AppSync para GraphQL

### Backend
- AppSync para API GraphQL
- DynamoDB para almacenamiento de datos
- Cognito para autenticación
- S3 para almacenamiento de archivos
- SNS para notificaciones
- CloudWatch para monitoreo

### Pipeline CI/CD
- CodeCommit para versionamiento
- CodeBuild para tests y build
- CodePipeline para automatización
- CloudFormation para IaC

### Monitoreo y Observabilidad
- CloudWatch Dashboards
- Métricas personalizadas
- Alertas automáticas
- Logs centralizados

## Flujo de Datos
1. Usuario accede a la landing page (Amplify Hosting)
2. Completa formulario de registro (React + Amplify UI)
3. Frontend envía datos vía GraphQL (AppSync)
4. Backend procesa y almacena datos (DynamoDB)
5. Se envían notificaciones configuradas (SNS)
6. Se registran métricas y logs (CloudWatch)

## Entornos
- DEV: Para desarrollo y pruebas
- STG: Para validación y QA
- PROD: Para producción

## Seguridad
- IAM para gestión de permisos
- Cognito para autenticación
- KMS para encriptación
- WAF para protección de API

2. Guía de Mantenimiento

markdown
# Guía de Mantenimiento y Operaciones

## Tareas Diarias
1. Revisar dashboards de CloudWatch
2. Verificar logs de errores
3. Monitorear métricas de performance
4. Validar tasas de conversión

## Tareas Semanales
1. Revisar métricas de seguridad
2. Analizar patrones de uso
3. Optimizar recursos según demanda
4. Verificar backups

## Tareas Mensuales
1. Revisar costos y optimización
2. Actualizar dependencias
3. Ejecutar pruebas de seguridad
4. Validar compliance

## Monitoreo
- Métricas clave a observar:
  * Tasa de conversión de registro
  * Tiempo de respuesta API
  * Errores de validación
  * Uso de recursos
  * Costos por ambiente

## Respuesta a Incidentes
1. Verificar alertas y logs
2. Identificar causa raíz
3. Aplicar solución o rollback
4. Documentar incidente
5. Implementar mejoras

## Optimización
1. Revisar métricas de rendimiento
2. Ajustar recursos según uso
3. Optimizar costos
4. Mejorar experiencia usuario

## Seguridad
1. Monitorear intentos de acceso
2. Revisar logs de seguridad
3. Actualizar políticas IAM
4. Mantener certificados

3. Documentación para Desarrolladores

markdown
# Guía para Desarrolladores

## Configuración Local
```bash
# Clonar repositorio
git clone <repo-url>

# Instalar dependencias
npm install

# Configurar Amplify
amplify pull --appId <app-id> --envName dev

# Iniciar desarrollo local
npm start

Flujo de Trabajo

  1. Crear branch desde dev
  2. Desarrollar y probar localmente
  3. Ejecutar tests unitarios
  4. Crear PR para revisión
  5. Merge a dev tras aprobación

Pruebas

bash
# Ejecutar tests unitarios
npm run test

# Ejecutar tests e2e
npm run test:e2e

# Verificar cobertura
npm run test:coverage

Despliegue

bash
# Desplegar a dev
npm run deploy:dev

# Desplegar a staging
npm run deploy:stg

# Desplegar a producción
npm run deploy:prod

Mejores Prácticas

  1. Seguir guías de estilo
  2. Documentar cambios
  3. Mantener tests actualizados
  4. Optimizar performance
  5. Seguir principios SOLID

Troubleshooting

  1. Verificar logs locales
  2. Usar herramientas de debug
  3. Consultar docs de Amplify
  4. Revisar issues comunes
## 4. Recomendaciones Finales

1. **Monitoreo Proactivo**
   - Establecer alertas preventivas
   - Mantener dashboards actualizados
   - Revisar métricas regularmente

2. **Seguridad**
   - Actualizar dependencias
   - Revisar configuraciones
   - Mantener políticas IAM

3. **Performance**
   - Optimizar queries GraphQL
   - Monitorear latencia
   - Ajustar recursos

4. **Costos**
   - Monitorear uso
   - Optimizar recursos
   - Revisar facturación

5. **Mejora Continua**
   - Analizar feedback
   - Implementar mejoras
   - Actualizar documentación